2013-12-03 10:27:23 +00:00
|
|
|
/*
|
|
|
|
* drivers/irqchip/irq-crossbar.c
|
|
|
|
*
|
|
|
|
* Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
|
|
|
|
* Author: Sricharan R <r.sricharan@ti.com>
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
|
|
#include <linux/io.h>
|
2015-07-07 21:11:46 +00:00
|
|
|
#include <linux/irqchip.h>
|
2015-03-11 15:43:44 +00:00
|
|
|
#include <linux/irqdomain.h>
|
2013-12-03 10:27:23 +00:00
|
|
|
#include <linux/of_address.h>
|
|
|
|
#include <linux/of_irq.h>
|
|
|
|
#include <linux/slab.h>
|
2015-03-11 15:43:44 +00:00
|
|
|
|
2013-12-03 10:27:23 +00:00
|
|
|
#define IRQ_FREE -1
|
2014-06-26 07:10:19 +00:00
|
|
|
#define IRQ_RESERVED -2
|
2014-06-26 07:10:21 +00:00
|
|
|
#define IRQ_SKIP -3
|
2013-12-03 10:27:23 +00:00
|
|
|
#define GIC_IRQ_START 32
|
|
|
|
|
2014-06-26 07:10:26 +00:00
|
|
|
/**
|
|
|
|
* struct crossbar_device - crossbar device description
|
2015-03-11 15:43:44 +00:00
|
|
|
* @lock: spinlock serializing access to @irq_map
|
2013-12-03 10:27:23 +00:00
|
|
|
* @int_max: maximum number of supported interrupts
|
2014-06-26 07:10:22 +00:00
|
|
|
* @safe_map: safe default value to initialize the crossbar
|
2014-06-26 07:10:31 +00:00
|
|
|
* @max_crossbar_sources: Maximum number of crossbar sources
|
2013-12-03 10:27:23 +00:00
|
|
|
* @irq_map: array of interrupts to crossbar number mapping
|
|
|
|
* @crossbar_base: crossbar base address
|
|
|
|
* @register_offsets: offsets for each irq number
|
2014-06-26 07:10:26 +00:00
|
|
|
* @write: register write function pointer
|
2013-12-03 10:27:23 +00:00
|
|
|
*/
|
|
|
|
struct crossbar_device {
|
2015-03-11 15:43:44 +00:00
|
|
|
raw_spinlock_t lock;
|
2013-12-03 10:27:23 +00:00
|
|
|
uint int_max;
|
2014-06-26 07:10:22 +00:00
|
|
|
uint safe_map;
|
2014-06-26 07:10:31 +00:00
|
|
|
uint max_crossbar_sources;
|
2013-12-03 10:27:23 +00:00
|
|
|
uint *irq_map;
|
|
|
|
void __iomem *crossbar_base;
|
|
|
|
int *register_offsets;
|
2014-06-26 07:10:22 +00:00
|
|
|
void (*write)(int, int);
|
2013-12-03 10:27:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct crossbar_device *cb;
|
|
|
|
|
2015-03-11 15:43:44 +00:00
|
|
|
static void crossbar_writel(int irq_no, int cb_no)
|
2013-12-03 10:27:23 +00:00
|
|
|
{
|
|
|
|
writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
|
|
|
|
}
|
|
|
|
|
2015-03-11 15:43:44 +00:00
|
|
|
static void crossbar_writew(int irq_no, int cb_no)
|
2013-12-03 10:27:23 +00:00
|
|
|
{
|
|
|
|
writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
|
|
|
|
}
|
|
|
|
|
2015-03-11 15:43:44 +00:00
|
|
|
static void crossbar_writeb(int irq_no, int cb_no)
|
2013-12-03 10:27:23 +00:00
|
|
|
{
|
|
|
|
writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
|
|
|
|
}
|
|
|
|
|
2015-03-11 15:43:44 +00:00
|
|
|
static struct irq_chip crossbar_chip = {
|
|
|
|
.name = "CBAR",
|
|
|
|
.irq_eoi = irq_chip_eoi_parent,
|
|
|
|
.irq_mask = irq_chip_mask_parent,
|
|
|
|
.irq_unmask = irq_chip_unmask_parent,
|
|
|
|
.irq_retrigger = irq_chip_retrigger_hierarchy,
|
2015-08-14 12:20:27 +00:00
|
|
|
.irq_set_type = irq_chip_set_type_parent,
|
2015-08-14 12:20:30 +00:00
|
|
|
.flags = IRQCHIP_MASK_ON_SUSPEND |
|
|
|
|
IRQCHIP_SKIP_SET_WAKE,
|
2015-03-11 15:43:44 +00:00
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
.irq_set_affinity = irq_chip_set_affinity_parent,
|
|
|
|
#endif
|
|
|
|
};
|
2014-06-26 07:10:20 +00:00
|
|
|
|
2015-03-11 15:43:44 +00:00
|
|
|
static int allocate_gic_irq(struct irq_domain *domain, unsigned virq,
|
|
|
|
irq_hw_number_t hwirq)
|
2013-12-03 10:27:23 +00:00
|
|
|
{
|
2015-03-11 15:43:44 +00:00
|
|
|
struct of_phandle_args args;
|
2013-12-03 10:27:23 +00:00
|
|
|
int i;
|
2015-03-11 15:43:44 +00:00
|
|
|
int err;
|
2013-12-03 10:27:23 +00:00
|
|
|
|
2015-03-11 15:43:44 +00:00
|
|
|
raw_spin_lock(&cb->lock);
|
2014-06-26 07:10:23 +00:00
|
|
|
for (i = cb->int_max - 1; i >= 0; i--) {
|
2013-12-03 10:27:23 +00:00
|
|
|
if (cb->irq_map[i] == IRQ_FREE) {
|
2015-03-11 15:43:44 +00:00
|
|
|
cb->irq_map[i] = hwirq;
|
|
|
|
break;
|
2013-12-03 10:27:23 +00:00
|
|
|
}
|
|
|
|
}
|
2015-03-11 15:43:44 +00:00
|
|
|
raw_spin_unlock(&cb->lock);
|
2013-12-03 10:27:23 +00:00
|
|
|
|
2015-03-11 15:43:44 +00:00
|
|
|
if (i < 0)
|
|
|
|
return -ENODEV;
|
2013-12-03 10:27:23 +00:00
|
|
|
|
2015-10-13 11:51:29 +00:00
|
|
|
args.np = irq_domain_get_of_node(domain->parent);
|
2015-03-11 15:43:44 +00:00
|
|
|
args.args_count = 3;
|
|
|
|
args.args[0] = 0; /* SPI */
|
|
|
|
args.args[1] = i;
|
|
|
|
args.args[2] = IRQ_TYPE_LEVEL_HIGH;
|
irqchip: crossbar: Allow for quirky hardware with direct hardwiring of GIC
On certain platforms such as DRA7, SPIs 0, 1, 2, 3, 5, 6, 10, 131,
132, 133 are direct wired to hardware blocks bypassing crossbar.
This quirky implementation is *NOT* supposed to be the expectation
of crossbar hardware usage. However, these are already marked in our
description of the hardware with SKIP and RESERVED where appropriate.
Unfortunately, we need to be able to refer to these hardwired IRQs.
So, to request these, crossbar driver can use the existing information
from it's table that these SKIP/RESERVED maps are direct wired sources
and generic allocation/programming of crossbar should be avoided.
Signed-off-by: Nishanth Menon <nm@ti.com>
Signed-off-by: Sricharan R <r.sricharan@ti.com>
Acked-by: Santosh Shilimkar <santosh.shilimkar@ti.com>
Link: https://lkml.kernel.org/r/1403766634-18543-17-git-send-email-r.sricharan@ti.com
Signed-off-by: Jason Cooper <jason@lakedaemon.net>
2014-06-26 07:10:34 +00:00
|
|
|
|
2015-03-11 15:43:44 +00:00
|
|
|
err = irq_domain_alloc_irqs_parent(domain, virq, 1, &args);
|
|
|
|
if (err)
|
|
|
|
cb->irq_map[i] = IRQ_FREE;
|
|
|
|
else
|
|
|
|
cb->write(i, hwirq);
|
2014-06-26 07:10:32 +00:00
|
|
|
|
2015-03-11 15:43:44 +00:00
|
|
|
return err;
|
2014-06-26 07:10:32 +00:00
|
|
|
}
|
|
|
|
|
2015-03-11 15:43:44 +00:00
|
|
|
static int crossbar_domain_alloc(struct irq_domain *d, unsigned int virq,
|
|
|
|
unsigned int nr_irqs, void *data)
|
2013-12-03 10:27:23 +00:00
|
|
|
{
|
2015-03-11 15:43:44 +00:00
|
|
|
struct of_phandle_args *args = data;
|
|
|
|
irq_hw_number_t hwirq;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (args->args_count != 3)
|
|
|
|
return -EINVAL; /* Not GIC compliant */
|
|
|
|
if (args->args[0] != 0)
|
|
|
|
return -EINVAL; /* No PPI should point to this domain */
|
|
|
|
|
|
|
|
hwirq = args->args[1];
|
|
|
|
if ((hwirq + nr_irqs) > cb->max_crossbar_sources)
|
|
|
|
return -EINVAL; /* Can't deal with this */
|
|
|
|
|
|
|
|
for (i = 0; i < nr_irqs; i++) {
|
|
|
|
int err = allocate_gic_irq(d, virq + i, hwirq + i);
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
irq_domain_set_hwirq_and_chip(d, virq + i, hwirq + i,
|
|
|
|
&crossbar_chip, NULL);
|
|
|
|
}
|
2014-06-26 07:10:32 +00:00
|
|
|
|
2013-12-03 10:27:23 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-06-26 07:10:30 +00:00
|
|
|
/**
|
2015-03-11 15:43:44 +00:00
|
|
|
* crossbar_domain_free - unmap/free a crossbar<->irq connection
|
|
|
|
* @domain: domain of irq to unmap
|
|
|
|
* @virq: virq number
|
|
|
|
* @nr_irqs: number of irqs to free
|
2014-06-26 07:10:30 +00:00
|
|
|
*
|
|
|
|
* We do not maintain a use count of total number of map/unmap
|
|
|
|
* calls for a particular irq to find out if a irq can be really
|
|
|
|
* unmapped. This is because unmap is called during irq_dispose_mapping(irq),
|
|
|
|
* after which irq is anyways unusable. So an explicit map has to be called
|
|
|
|
* after that.
|
|
|
|
*/
|
2015-03-11 15:43:44 +00:00
|
|
|
static void crossbar_domain_free(struct irq_domain *domain, unsigned int virq,
|
|
|
|
unsigned int nr_irqs)
|
2013-12-03 10:27:23 +00:00
|
|
|
{
|
2015-03-11 15:43:44 +00:00
|
|
|
int i;
|
2013-12-03 10:27:23 +00:00
|
|
|
|
2015-03-11 15:43:44 +00:00
|
|
|
raw_spin_lock(&cb->lock);
|
|
|
|
for (i = 0; i < nr_irqs; i++) {
|
|
|
|
struct irq_data *d = irq_domain_get_irq_data(domain, virq + i);
|
|
|
|
|
|
|
|
irq_domain_reset_irq_data(d);
|
|
|
|
cb->irq_map[d->hwirq] = IRQ_FREE;
|
|
|
|
cb->write(d->hwirq, cb->safe_map);
|
2014-06-26 07:10:22 +00:00
|
|
|
}
|
2015-03-11 15:43:44 +00:00
|
|
|
raw_spin_unlock(&cb->lock);
|
2013-12-03 10:27:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int crossbar_domain_xlate(struct irq_domain *d,
|
|
|
|
struct device_node *controller,
|
|
|
|
const u32 *intspec, unsigned int intsize,
|
|
|
|
unsigned long *out_hwirq,
|
|
|
|
unsigned int *out_type)
|
|
|
|
{
|
2015-10-13 11:51:29 +00:00
|
|
|
if (irq_domain_get_of_node(d) != controller)
|
2015-03-11 15:43:44 +00:00
|
|
|
return -EINVAL; /* Shouldn't happen, really... */
|
|
|
|
if (intsize != 3)
|
|
|
|
return -EINVAL; /* Not GIC compliant */
|
|
|
|
if (intspec[0] != 0)
|
|
|
|
return -EINVAL; /* No PPI should point to this domain */
|
|
|
|
|
|
|
|
*out_hwirq = intspec[1];
|
|
|
|
*out_type = intspec[2];
|
2013-12-03 10:27:23 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-03-11 15:43:44 +00:00
|
|
|
static const struct irq_domain_ops crossbar_domain_ops = {
|
|
|
|
.alloc = crossbar_domain_alloc,
|
|
|
|
.free = crossbar_domain_free,
|
|
|
|
.xlate = crossbar_domain_xlate,
|
2013-12-03 10:27:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static int __init crossbar_of_init(struct device_node *node)
|
|
|
|
{
|
2014-06-26 07:10:27 +00:00
|
|
|
int i, size, max = 0, reserved = 0, entry;
|
2013-12-03 10:27:23 +00:00
|
|
|
const __be32 *irqsr;
|
2014-06-26 07:10:27 +00:00
|
|
|
int ret = -ENOMEM;
|
2013-12-03 10:27:23 +00:00
|
|
|
|
2014-04-03 07:21:34 +00:00
|
|
|
cb = kzalloc(sizeof(*cb), GFP_KERNEL);
|
2013-12-03 10:27:23 +00:00
|
|
|
|
|
|
|
if (!cb)
|
2014-06-26 07:10:27 +00:00
|
|
|
return ret;
|
2013-12-03 10:27:23 +00:00
|
|
|
|
|
|
|
cb->crossbar_base = of_iomap(node, 0);
|
|
|
|
if (!cb->crossbar_base)
|
2014-06-26 07:10:28 +00:00
|
|
|
goto err_cb;
|
2013-12-03 10:27:23 +00:00
|
|
|
|
2014-06-26 07:10:31 +00:00
|
|
|
of_property_read_u32(node, "ti,max-crossbar-sources",
|
|
|
|
&cb->max_crossbar_sources);
|
|
|
|
if (!cb->max_crossbar_sources) {
|
|
|
|
pr_err("missing 'ti,max-crossbar-sources' property\n");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto err_base;
|
|
|
|
}
|
|
|
|
|
2013-12-03 10:27:23 +00:00
|
|
|
of_property_read_u32(node, "ti,max-irqs", &max);
|
2014-06-26 07:10:27 +00:00
|
|
|
if (!max) {
|
|
|
|
pr_err("missing 'ti,max-irqs' property\n");
|
|
|
|
ret = -EINVAL;
|
2014-06-26 07:10:28 +00:00
|
|
|
goto err_base;
|
2014-06-26 07:10:27 +00:00
|
|
|
}
|
2014-06-26 07:10:25 +00:00
|
|
|
cb->irq_map = kcalloc(max, sizeof(int), GFP_KERNEL);
|
2013-12-03 10:27:23 +00:00
|
|
|
if (!cb->irq_map)
|
2014-06-26 07:10:28 +00:00
|
|
|
goto err_base;
|
2013-12-03 10:27:23 +00:00
|
|
|
|
|
|
|
cb->int_max = max;
|
|
|
|
|
|
|
|
for (i = 0; i < max; i++)
|
|
|
|
cb->irq_map[i] = IRQ_FREE;
|
|
|
|
|
|
|
|
/* Get and mark reserved irqs */
|
|
|
|
irqsr = of_get_property(node, "ti,irqs-reserved", &size);
|
|
|
|
if (irqsr) {
|
|
|
|
size /= sizeof(__be32);
|
|
|
|
|
|
|
|
for (i = 0; i < size; i++) {
|
|
|
|
of_property_read_u32_index(node,
|
|
|
|
"ti,irqs-reserved",
|
|
|
|
i, &entry);
|
2014-08-07 15:28:21 +00:00
|
|
|
if (entry >= max) {
|
2013-12-03 10:27:23 +00:00
|
|
|
pr_err("Invalid reserved entry\n");
|
2014-06-26 07:10:27 +00:00
|
|
|
ret = -EINVAL;
|
2014-06-26 07:10:28 +00:00
|
|
|
goto err_irq_map;
|
2013-12-03 10:27:23 +00:00
|
|
|
}
|
2014-06-26 07:10:19 +00:00
|
|
|
cb->irq_map[entry] = IRQ_RESERVED;
|
2013-12-03 10:27:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-26 07:10:21 +00:00
|
|
|
/* Skip irqs hardwired to bypass the crossbar */
|
|
|
|
irqsr = of_get_property(node, "ti,irqs-skip", &size);
|
|
|
|
if (irqsr) {
|
|
|
|
size /= sizeof(__be32);
|
|
|
|
|
|
|
|
for (i = 0; i < size; i++) {
|
|
|
|
of_property_read_u32_index(node,
|
|
|
|
"ti,irqs-skip",
|
|
|
|
i, &entry);
|
2014-08-07 15:28:21 +00:00
|
|
|
if (entry >= max) {
|
2014-06-26 07:10:21 +00:00
|
|
|
pr_err("Invalid skip entry\n");
|
|
|
|
ret = -EINVAL;
|
2014-06-26 07:10:28 +00:00
|
|
|
goto err_irq_map;
|
2014-06-26 07:10:21 +00:00
|
|
|
}
|
|
|
|
cb->irq_map[entry] = IRQ_SKIP;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-06-26 07:10:25 +00:00
|
|
|
cb->register_offsets = kcalloc(max, sizeof(int), GFP_KERNEL);
|
2013-12-03 10:27:23 +00:00
|
|
|
if (!cb->register_offsets)
|
2014-06-26 07:10:28 +00:00
|
|
|
goto err_irq_map;
|
2013-12-03 10:27:23 +00:00
|
|
|
|
|
|
|
of_property_read_u32(node, "ti,reg-size", &size);
|
|
|
|
|
|
|
|
switch (size) {
|
|
|
|
case 1:
|
|
|
|
cb->write = crossbar_writeb;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
cb->write = crossbar_writew;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
cb->write = crossbar_writel;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
pr_err("Invalid reg-size property\n");
|
2014-06-26 07:10:27 +00:00
|
|
|
ret = -EINVAL;
|
2014-06-26 07:10:28 +00:00
|
|
|
goto err_reg_offset;
|
2013-12-03 10:27:23 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Register offsets are not linear because of the
|
|
|
|
* reserved irqs. so find and store the offsets once.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < max; i++) {
|
2014-06-26 07:10:19 +00:00
|
|
|
if (cb->irq_map[i] == IRQ_RESERVED)
|
2013-12-03 10:27:23 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
cb->register_offsets[i] = reserved;
|
|
|
|
reserved += size;
|
|
|
|
}
|
|
|
|
|
2014-06-26 07:10:22 +00:00
|
|
|
of_property_read_u32(node, "ti,irqs-safe-map", &cb->safe_map);
|
|
|
|
/* Initialize the crossbar with safe map to start with */
|
|
|
|
for (i = 0; i < max; i++) {
|
|
|
|
if (cb->irq_map[i] == IRQ_RESERVED ||
|
|
|
|
cb->irq_map[i] == IRQ_SKIP)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
cb->write(i, cb->safe_map);
|
|
|
|
}
|
|
|
|
|
2015-03-11 15:43:44 +00:00
|
|
|
raw_spin_lock_init(&cb->lock);
|
|
|
|
|
2013-12-03 10:27:23 +00:00
|
|
|
return 0;
|
|
|
|
|
2014-06-26 07:10:28 +00:00
|
|
|
err_reg_offset:
|
2013-12-03 10:27:23 +00:00
|
|
|
kfree(cb->register_offsets);
|
2014-06-26 07:10:28 +00:00
|
|
|
err_irq_map:
|
2013-12-03 10:27:23 +00:00
|
|
|
kfree(cb->irq_map);
|
2014-06-26 07:10:28 +00:00
|
|
|
err_base:
|
2013-12-03 10:27:23 +00:00
|
|
|
iounmap(cb->crossbar_base);
|
2014-06-26 07:10:28 +00:00
|
|
|
err_cb:
|
2013-12-03 10:27:23 +00:00
|
|
|
kfree(cb);
|
2014-06-26 07:10:29 +00:00
|
|
|
|
|
|
|
cb = NULL;
|
2014-06-26 07:10:27 +00:00
|
|
|
return ret;
|
2013-12-03 10:27:23 +00:00
|
|
|
}
|
|
|
|
|
2015-03-11 15:43:44 +00:00
|
|
|
static int __init irqcrossbar_init(struct device_node *node,
|
|
|
|
struct device_node *parent)
|
2013-12-03 10:27:23 +00:00
|
|
|
{
|
2015-03-11 15:43:44 +00:00
|
|
|
struct irq_domain *parent_domain, *domain;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (!parent) {
|
|
|
|
pr_err("%s: no parent, giving up\n", node->full_name);
|
2013-12-03 10:27:23 +00:00
|
|
|
return -ENODEV;
|
2015-03-11 15:43:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
parent_domain = irq_find_host(parent);
|
|
|
|
if (!parent_domain) {
|
|
|
|
pr_err("%s: unable to obtain parent domain\n", node->full_name);
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = crossbar_of_init(node);
|
|
|
|
if (err)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
domain = irq_domain_add_hierarchy(parent_domain, 0,
|
|
|
|
cb->max_crossbar_sources,
|
|
|
|
node, &crossbar_domain_ops,
|
|
|
|
NULL);
|
|
|
|
if (!domain) {
|
|
|
|
pr_err("%s: failed to allocated domain\n", node->full_name);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2013-12-03 10:27:23 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2015-03-11 15:43:44 +00:00
|
|
|
|
|
|
|
IRQCHIP_DECLARE(ti_irqcrossbar, "ti,irq-crossbar", irqcrossbar_init);
|