mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-29 11:11:45 -04:00
irqchip core change for v4.1 (round 3)
- Purge the gic_arch_extn hacks and abuse by using the new stacked domains
NOTE: Due to the nature of these changes, patches crossing subsystems have
been kept together in their own branches.
- tegra
- Handle the LIC properly
- omap
- Convert crossbar to stacked domains
- kill arm,routable-irqs in GIC binding
- exynos
- Convert PMU wakeup to stacked domains
- shmobile, ux500, zynq (irq_set_wake branch)
- Switch from abusing gic_arch_extn to using gic_set_irqchip_flags
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2
iQIcBAABAgAGBQJVKFhRAAoJEP45WPkGe8ZnYFcP/iBznjkMYG+OUwrxo7G4rTyu
JYj0dmg/D76ewFsxWFv24II9V+KJaqrEtFTHH4MVbeEbbrDIx7Am0i/Ip6rDRgxS
7Q/jGic8etfPGV8gW6x38zbTHOl1rfqQtoHcqBH5FnLITuMAuHPa51jpwhMik4ri
AbMwb6Whep6tEsxiEjspPxXWphEZoXluOkRjPLokTwuifo4rEo7bqU8WMizzSW5g
xEjf8eUvBYIMTA40FBQWHQwxf1jRySSW2A9u5JgT1ccZHoajEyDgQr22KUHpCAWU
hlZ/8uTqCUeecDQKFPr4zXhq9mbEVZ7lld5Gl82cxY6aI3Xj/bUI3tSYubPWEgx6
0VhbmvjqKPiFfdCrLq5ZTY5UHmW8khdttdycIPNz9LmUDVgIzJpmpAW+oyG7BN/N
QgGF4lzaN49mHQmjtXGfwY3iJTadxyVaWoZTBinjw8LyxpzUO/MNQGLumsxEtkxN
Nbbsc2k+ERpSx40ospB1WOslAzMsNi6eLwqLRfjGGfSYK1P6Mm7FhansJm08p1/D
8h6ymqA4heZrYdI1vrfuy7QuEqQgnVUf0TDTHxX+aNGrHnBSsPTTfYHBOHXUh4Cr
Ox3yLECAhWle4VlgInu3XLRmuUiYGk4JV4nbZUjpZvIaOZV4gLArcsQU7C/KTDT8
CqrybDOIxFkIbxfU+EE0
=IPgJ
-----END PGP SIGNATURE-----
Merge tag 'irqchip-core-4.1-3' of git://git.infradead.org/users/jcooper/linux into irq/core
irqchip core change for v4.1 (round 3) from Jason Cooper
Purge the gic_arch_extn hacks and abuse by using the new stacked domains
NOTE: Due to the nature of these changes, patches crossing subsystems have
been kept together in their own branches.
- tegra
- Handle the LIC properly
- omap
- Convert crossbar to stacked domains
- kill arm,routable-irqs in GIC binding
- exynos
- Convert PMU wakeup to stacked domains
- shmobile, ux500, zynq (irq_set_wake branch)
- Switch from abusing gic_arch_extn to using gic_set_irqchip_flags
This commit is contained in:
commit
b7dccbea6b
52 changed files with 1032 additions and 538 deletions
|
|
@ -6,6 +6,7 @@ obj-$(CONFIG_ARCH_HIP04) += irq-hip04.o
|
|||
obj-$(CONFIG_ARCH_MMP) += irq-mmp.o
|
||||
obj-$(CONFIG_ARCH_MVEBU) += irq-armada-370-xp.o
|
||||
obj-$(CONFIG_ARCH_MXS) += irq-mxs.o
|
||||
obj-$(CONFIG_ARCH_TEGRA) += irq-tegra.o
|
||||
obj-$(CONFIG_ARCH_S3C24XX) += irq-s3c24xx.o
|
||||
obj-$(CONFIG_DW_APB_ICTL) += irq-dw-apb-ictl.o
|
||||
obj-$(CONFIG_METAG) += irq-metag-ext.o
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@
|
|||
*/
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/irqchip/arm-gic.h>
|
||||
#include <linux/irqchip/irq-crossbar.h>
|
||||
|
||||
#include "irqchip.h"
|
||||
|
||||
#define IRQ_FREE -1
|
||||
#define IRQ_RESERVED -2
|
||||
|
|
@ -24,6 +25,7 @@
|
|||
|
||||
/**
|
||||
* struct crossbar_device - crossbar device description
|
||||
* @lock: spinlock serializing access to @irq_map
|
||||
* @int_max: maximum number of supported interrupts
|
||||
* @safe_map: safe default value to initialize the crossbar
|
||||
* @max_crossbar_sources: Maximum number of crossbar sources
|
||||
|
|
@ -33,6 +35,7 @@
|
|||
* @write: register write function pointer
|
||||
*/
|
||||
struct crossbar_device {
|
||||
raw_spinlock_t lock;
|
||||
uint int_max;
|
||||
uint safe_map;
|
||||
uint max_crossbar_sources;
|
||||
|
|
@ -44,72 +47,101 @@ struct crossbar_device {
|
|||
|
||||
static struct crossbar_device *cb;
|
||||
|
||||
static inline void crossbar_writel(int irq_no, int cb_no)
|
||||
static void crossbar_writel(int irq_no, int cb_no)
|
||||
{
|
||||
writel(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
|
||||
}
|
||||
|
||||
static inline void crossbar_writew(int irq_no, int cb_no)
|
||||
static void crossbar_writew(int irq_no, int cb_no)
|
||||
{
|
||||
writew(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
|
||||
}
|
||||
|
||||
static inline void crossbar_writeb(int irq_no, int cb_no)
|
||||
static void crossbar_writeb(int irq_no, int cb_no)
|
||||
{
|
||||
writeb(cb_no, cb->crossbar_base + cb->register_offsets[irq_no]);
|
||||
}
|
||||
|
||||
static inline int get_prev_map_irq(int cb_no)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = cb->int_max - 1; i >= 0; i--)
|
||||
if (cb->irq_map[i] == cb_no)
|
||||
return i;
|
||||
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static inline int allocate_free_irq(int cb_no)
|
||||
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,
|
||||
.irq_set_wake = irq_chip_set_wake_parent,
|
||||
#ifdef CONFIG_SMP
|
||||
.irq_set_affinity = irq_chip_set_affinity_parent,
|
||||
#endif
|
||||
};
|
||||
|
||||
static int allocate_gic_irq(struct irq_domain *domain, unsigned virq,
|
||||
irq_hw_number_t hwirq)
|
||||
{
|
||||
struct of_phandle_args args;
|
||||
int i;
|
||||
int err;
|
||||
|
||||
raw_spin_lock(&cb->lock);
|
||||
for (i = cb->int_max - 1; i >= 0; i--) {
|
||||
if (cb->irq_map[i] == IRQ_FREE) {
|
||||
cb->irq_map[i] = cb_no;
|
||||
return i;
|
||||
cb->irq_map[i] = hwirq;
|
||||
break;
|
||||
}
|
||||
}
|
||||
raw_spin_unlock(&cb->lock);
|
||||
|
||||
return -ENODEV;
|
||||
if (i < 0)
|
||||
return -ENODEV;
|
||||
|
||||
args.np = domain->parent->of_node;
|
||||
args.args_count = 3;
|
||||
args.args[0] = 0; /* SPI */
|
||||
args.args[1] = i;
|
||||
args.args[2] = IRQ_TYPE_LEVEL_HIGH;
|
||||
|
||||
err = irq_domain_alloc_irqs_parent(domain, virq, 1, &args);
|
||||
if (err)
|
||||
cb->irq_map[i] = IRQ_FREE;
|
||||
else
|
||||
cb->write(i, hwirq);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline bool needs_crossbar_write(irq_hw_number_t hw)
|
||||
static int crossbar_domain_alloc(struct irq_domain *d, unsigned int virq,
|
||||
unsigned int nr_irqs, void *data)
|
||||
{
|
||||
int cb_no;
|
||||
struct of_phandle_args *args = data;
|
||||
irq_hw_number_t hwirq;
|
||||
int i;
|
||||
|
||||
if (hw > GIC_IRQ_START) {
|
||||
cb_no = cb->irq_map[hw - GIC_IRQ_START];
|
||||
if (cb_no != IRQ_RESERVED && cb_no != IRQ_SKIP)
|
||||
return true;
|
||||
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);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int crossbar_domain_map(struct irq_domain *d, unsigned int irq,
|
||||
irq_hw_number_t hw)
|
||||
{
|
||||
if (needs_crossbar_write(hw))
|
||||
cb->write(hw - GIC_IRQ_START, cb->irq_map[hw - GIC_IRQ_START]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* crossbar_domain_unmap - unmap a crossbar<->irq connection
|
||||
* @d: domain of irq to unmap
|
||||
* @irq: virq number
|
||||
* 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
|
||||
*
|
||||
* 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
|
||||
|
|
@ -117,14 +149,20 @@ static int crossbar_domain_map(struct irq_domain *d, unsigned int irq,
|
|||
* after which irq is anyways unusable. So an explicit map has to be called
|
||||
* after that.
|
||||
*/
|
||||
static void crossbar_domain_unmap(struct irq_domain *d, unsigned int irq)
|
||||
static void crossbar_domain_free(struct irq_domain *domain, unsigned int virq,
|
||||
unsigned int nr_irqs)
|
||||
{
|
||||
irq_hw_number_t hw = irq_get_irq_data(irq)->hwirq;
|
||||
int i;
|
||||
|
||||
if (needs_crossbar_write(hw)) {
|
||||
cb->irq_map[hw - GIC_IRQ_START] = IRQ_FREE;
|
||||
cb->write(hw - GIC_IRQ_START, cb->safe_map);
|
||||
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);
|
||||
}
|
||||
raw_spin_unlock(&cb->lock);
|
||||
}
|
||||
|
||||
static int crossbar_domain_xlate(struct irq_domain *d,
|
||||
|
|
@ -133,44 +171,22 @@ static int crossbar_domain_xlate(struct irq_domain *d,
|
|||
unsigned long *out_hwirq,
|
||||
unsigned int *out_type)
|
||||
{
|
||||
int ret;
|
||||
int req_num = intspec[1];
|
||||
int direct_map_num;
|
||||
if (d->of_node != controller)
|
||||
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 */
|
||||
|
||||
if (req_num >= cb->max_crossbar_sources) {
|
||||
direct_map_num = req_num - cb->max_crossbar_sources;
|
||||
if (direct_map_num < cb->int_max) {
|
||||
ret = cb->irq_map[direct_map_num];
|
||||
if (ret == IRQ_RESERVED || ret == IRQ_SKIP) {
|
||||
/* We use the interrupt num as h/w irq num */
|
||||
ret = direct_map_num;
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
|
||||
pr_err("%s: requested crossbar number %d > max %d\n",
|
||||
__func__, req_num, cb->max_crossbar_sources);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = get_prev_map_irq(req_num);
|
||||
if (ret >= 0)
|
||||
goto found;
|
||||
|
||||
ret = allocate_free_irq(req_num);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
found:
|
||||
*out_hwirq = ret + GIC_IRQ_START;
|
||||
*out_hwirq = intspec[1];
|
||||
*out_type = intspec[2];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops routable_irq_domain_ops = {
|
||||
.map = crossbar_domain_map,
|
||||
.unmap = crossbar_domain_unmap,
|
||||
.xlate = crossbar_domain_xlate
|
||||
static const struct irq_domain_ops crossbar_domain_ops = {
|
||||
.alloc = crossbar_domain_alloc,
|
||||
.free = crossbar_domain_free,
|
||||
.xlate = crossbar_domain_xlate,
|
||||
};
|
||||
|
||||
static int __init crossbar_of_init(struct device_node *node)
|
||||
|
|
@ -293,7 +309,8 @@ static int __init crossbar_of_init(struct device_node *node)
|
|||
cb->write(i, cb->safe_map);
|
||||
}
|
||||
|
||||
register_routable_domain_ops(&routable_irq_domain_ops);
|
||||
raw_spin_lock_init(&cb->lock);
|
||||
|
||||
return 0;
|
||||
|
||||
err_reg_offset:
|
||||
|
|
@ -309,18 +326,37 @@ err_cb:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id crossbar_match[] __initconst = {
|
||||
{ .compatible = "ti,irq-crossbar" },
|
||||
{}
|
||||
};
|
||||
|
||||
int __init irqcrossbar_init(void)
|
||||
static int __init irqcrossbar_init(struct device_node *node,
|
||||
struct device_node *parent)
|
||||
{
|
||||
struct device_node *np;
|
||||
np = of_find_matching_node(NULL, crossbar_match);
|
||||
if (!np)
|
||||
return -ENODEV;
|
||||
struct irq_domain *parent_domain, *domain;
|
||||
int err;
|
||||
|
||||
if (!parent) {
|
||||
pr_err("%s: no parent, giving up\n", node->full_name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
crossbar_of_init(np);
|
||||
return 0;
|
||||
}
|
||||
|
||||
IRQCHIP_DECLARE(ti_irqcrossbar, "ti,irq-crossbar", irqcrossbar_init);
|
||||
|
|
|
|||
|
|
@ -863,15 +863,12 @@ static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
|
|||
irq_domain_set_info(d, irq, hw, &gic_chip, d->host_data,
|
||||
handle_fasteoi_irq, NULL, NULL);
|
||||
set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
|
||||
|
||||
gic_routable_irq_domain_ops->map(d, irq, hw);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gic_irq_domain_unmap(struct irq_domain *d, unsigned int irq)
|
||||
{
|
||||
gic_routable_irq_domain_ops->unmap(d, irq);
|
||||
}
|
||||
|
||||
static int gic_irq_domain_xlate(struct irq_domain *d,
|
||||
|
|
@ -890,16 +887,8 @@ static int gic_irq_domain_xlate(struct irq_domain *d,
|
|||
*out_hwirq = intspec[1] + 16;
|
||||
|
||||
/* For SPIs, we need to add 16 more to get the GIC irq ID number */
|
||||
if (!intspec[0]) {
|
||||
ret = gic_routable_irq_domain_ops->xlate(d, controller,
|
||||
intspec,
|
||||
intsize,
|
||||
out_hwirq,
|
||||
out_type);
|
||||
|
||||
if (IS_ERR_VALUE(ret))
|
||||
return ret;
|
||||
}
|
||||
if (!intspec[0])
|
||||
*out_hwirq += 16;
|
||||
|
||||
*out_type = intspec[2] & IRQ_TYPE_SENSE_MASK;
|
||||
|
||||
|
|
@ -956,37 +945,11 @@ static const struct irq_domain_ops gic_irq_domain_ops = {
|
|||
.xlate = gic_irq_domain_xlate,
|
||||
};
|
||||
|
||||
/* Default functions for routable irq domain */
|
||||
static int gic_routable_irq_domain_map(struct irq_domain *d, unsigned int irq,
|
||||
irq_hw_number_t hw)
|
||||
void gic_set_irqchip_flags(unsigned long flags)
|
||||
{
|
||||
return 0;
|
||||
gic_chip.flags |= flags;
|
||||
}
|
||||
|
||||
static void gic_routable_irq_domain_unmap(struct irq_domain *d,
|
||||
unsigned int irq)
|
||||
{
|
||||
}
|
||||
|
||||
static int gic_routable_irq_domain_xlate(struct irq_domain *d,
|
||||
struct device_node *controller,
|
||||
const u32 *intspec, unsigned int intsize,
|
||||
unsigned long *out_hwirq,
|
||||
unsigned int *out_type)
|
||||
{
|
||||
*out_hwirq += 16;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops gic_default_routable_irq_domain_ops = {
|
||||
.map = gic_routable_irq_domain_map,
|
||||
.unmap = gic_routable_irq_domain_unmap,
|
||||
.xlate = gic_routable_irq_domain_xlate,
|
||||
};
|
||||
|
||||
const struct irq_domain_ops *gic_routable_irq_domain_ops =
|
||||
&gic_default_routable_irq_domain_ops;
|
||||
|
||||
void __init gic_init_bases(unsigned int gic_nr, int irq_start,
|
||||
void __iomem *dist_base, void __iomem *cpu_base,
|
||||
u32 percpu_offset, struct device_node *node)
|
||||
|
|
@ -994,7 +957,6 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start,
|
|||
irq_hw_number_t hwirq_base;
|
||||
struct gic_chip_data *gic;
|
||||
int gic_irqs, irq_base, i;
|
||||
int nr_routable_irqs;
|
||||
|
||||
BUG_ON(gic_nr >= MAX_GIC_NR);
|
||||
|
||||
|
|
@ -1050,15 +1012,9 @@ void __init gic_init_bases(unsigned int gic_nr, int irq_start,
|
|||
gic->gic_irqs = gic_irqs;
|
||||
|
||||
if (node) { /* DT case */
|
||||
const struct irq_domain_ops *ops = &gic_irq_domain_hierarchy_ops;
|
||||
|
||||
if (!of_property_read_u32(node, "arm,routable-irqs",
|
||||
&nr_routable_irqs)) {
|
||||
ops = &gic_irq_domain_ops;
|
||||
gic_irqs = nr_routable_irqs;
|
||||
}
|
||||
|
||||
gic->domain = irq_domain_add_linear(node, gic_irqs, ops, gic);
|
||||
gic->domain = irq_domain_add_linear(node, gic_irqs,
|
||||
&gic_irq_domain_hierarchy_ops,
|
||||
gic);
|
||||
} else { /* Non-DT case */
|
||||
/*
|
||||
* For primary GICs, skip over SGIs.
|
||||
|
|
|
|||
377
drivers/irqchip/irq-tegra.c
Normal file
377
drivers/irqchip/irq-tegra.c
Normal file
|
|
@ -0,0 +1,377 @@
|
|||
/*
|
||||
* Driver code for Tegra's Legacy Interrupt Controller
|
||||
*
|
||||
* Author: Marc Zyngier <marc.zyngier@arm.com>
|
||||
*
|
||||
* Heavily based on the original arch/arm/mach-tegra/irq.c code:
|
||||
* Copyright (C) 2011 Google, Inc.
|
||||
*
|
||||
* Author:
|
||||
* Colin Cross <ccross@android.com>
|
||||
*
|
||||
* Copyright (C) 2010,2013, NVIDIA Corporation
|
||||
*
|
||||
* This software is licensed under the terms of the GNU General Public
|
||||
* License version 2, as published by the Free Software Foundation, and
|
||||
* may be copied, distributed, and modified under those terms.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/syscore_ops.h>
|
||||
|
||||
#include <dt-bindings/interrupt-controller/arm-gic.h>
|
||||
|
||||
#include "irqchip.h"
|
||||
|
||||
#define ICTLR_CPU_IEP_VFIQ 0x08
|
||||
#define ICTLR_CPU_IEP_FIR 0x14
|
||||
#define ICTLR_CPU_IEP_FIR_SET 0x18
|
||||
#define ICTLR_CPU_IEP_FIR_CLR 0x1c
|
||||
|
||||
#define ICTLR_CPU_IER 0x20
|
||||
#define ICTLR_CPU_IER_SET 0x24
|
||||
#define ICTLR_CPU_IER_CLR 0x28
|
||||
#define ICTLR_CPU_IEP_CLASS 0x2C
|
||||
|
||||
#define ICTLR_COP_IER 0x30
|
||||
#define ICTLR_COP_IER_SET 0x34
|
||||
#define ICTLR_COP_IER_CLR 0x38
|
||||
#define ICTLR_COP_IEP_CLASS 0x3c
|
||||
|
||||
#define TEGRA_MAX_NUM_ICTLRS 6
|
||||
|
||||
static unsigned int num_ictlrs;
|
||||
|
||||
struct tegra_ictlr_soc {
|
||||
unsigned int num_ictlrs;
|
||||
};
|
||||
|
||||
static const struct tegra_ictlr_soc tegra20_ictlr_soc = {
|
||||
.num_ictlrs = 4,
|
||||
};
|
||||
|
||||
static const struct tegra_ictlr_soc tegra30_ictlr_soc = {
|
||||
.num_ictlrs = 5,
|
||||
};
|
||||
|
||||
static const struct tegra_ictlr_soc tegra210_ictlr_soc = {
|
||||
.num_ictlrs = 6,
|
||||
};
|
||||
|
||||
static const struct of_device_id ictlr_matches[] = {
|
||||
{ .compatible = "nvidia,tegra210-ictlr", .data = &tegra210_ictlr_soc },
|
||||
{ .compatible = "nvidia,tegra30-ictlr", .data = &tegra30_ictlr_soc },
|
||||
{ .compatible = "nvidia,tegra20-ictlr", .data = &tegra20_ictlr_soc },
|
||||
{ }
|
||||
};
|
||||
|
||||
struct tegra_ictlr_info {
|
||||
void __iomem *base[TEGRA_MAX_NUM_ICTLRS];
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
u32 cop_ier[TEGRA_MAX_NUM_ICTLRS];
|
||||
u32 cop_iep[TEGRA_MAX_NUM_ICTLRS];
|
||||
u32 cpu_ier[TEGRA_MAX_NUM_ICTLRS];
|
||||
u32 cpu_iep[TEGRA_MAX_NUM_ICTLRS];
|
||||
|
||||
u32 ictlr_wake_mask[TEGRA_MAX_NUM_ICTLRS];
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct tegra_ictlr_info *lic;
|
||||
|
||||
static inline void tegra_ictlr_write_mask(struct irq_data *d, unsigned long reg)
|
||||
{
|
||||
void __iomem *base = d->chip_data;
|
||||
u32 mask;
|
||||
|
||||
mask = BIT(d->hwirq % 32);
|
||||
writel_relaxed(mask, base + reg);
|
||||
}
|
||||
|
||||
static void tegra_mask(struct irq_data *d)
|
||||
{
|
||||
tegra_ictlr_write_mask(d, ICTLR_CPU_IER_CLR);
|
||||
irq_chip_mask_parent(d);
|
||||
}
|
||||
|
||||
static void tegra_unmask(struct irq_data *d)
|
||||
{
|
||||
tegra_ictlr_write_mask(d, ICTLR_CPU_IER_SET);
|
||||
irq_chip_unmask_parent(d);
|
||||
}
|
||||
|
||||
static void tegra_eoi(struct irq_data *d)
|
||||
{
|
||||
tegra_ictlr_write_mask(d, ICTLR_CPU_IEP_FIR_CLR);
|
||||
irq_chip_eoi_parent(d);
|
||||
}
|
||||
|
||||
static int tegra_retrigger(struct irq_data *d)
|
||||
{
|
||||
tegra_ictlr_write_mask(d, ICTLR_CPU_IEP_FIR_SET);
|
||||
return irq_chip_retrigger_hierarchy(d);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int tegra_set_wake(struct irq_data *d, unsigned int enable)
|
||||
{
|
||||
u32 irq = d->hwirq;
|
||||
u32 index, mask;
|
||||
|
||||
index = (irq / 32);
|
||||
mask = BIT(irq % 32);
|
||||
if (enable)
|
||||
lic->ictlr_wake_mask[index] |= mask;
|
||||
else
|
||||
lic->ictlr_wake_mask[index] &= ~mask;
|
||||
|
||||
/*
|
||||
* Do *not* call into the parent, as the GIC doesn't have any
|
||||
* wake-up facility...
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_ictlr_suspend(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned int i;
|
||||
|
||||
local_irq_save(flags);
|
||||
for (i = 0; i < num_ictlrs; i++) {
|
||||
void __iomem *ictlr = lic->base[i];
|
||||
|
||||
/* Save interrupt state */
|
||||
lic->cpu_ier[i] = readl_relaxed(ictlr + ICTLR_CPU_IER);
|
||||
lic->cpu_iep[i] = readl_relaxed(ictlr + ICTLR_CPU_IEP_CLASS);
|
||||
lic->cop_ier[i] = readl_relaxed(ictlr + ICTLR_COP_IER);
|
||||
lic->cop_iep[i] = readl_relaxed(ictlr + ICTLR_COP_IEP_CLASS);
|
||||
|
||||
/* Disable COP interrupts */
|
||||
writel_relaxed(~0ul, ictlr + ICTLR_COP_IER_CLR);
|
||||
|
||||
/* Disable CPU interrupts */
|
||||
writel_relaxed(~0ul, ictlr + ICTLR_CPU_IER_CLR);
|
||||
|
||||
/* Enable the wakeup sources of ictlr */
|
||||
writel_relaxed(lic->ictlr_wake_mask[i], ictlr + ICTLR_CPU_IER_SET);
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tegra_ictlr_resume(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned int i;
|
||||
|
||||
local_irq_save(flags);
|
||||
for (i = 0; i < num_ictlrs; i++) {
|
||||
void __iomem *ictlr = lic->base[i];
|
||||
|
||||
writel_relaxed(lic->cpu_iep[i],
|
||||
ictlr + ICTLR_CPU_IEP_CLASS);
|
||||
writel_relaxed(~0ul, ictlr + ICTLR_CPU_IER_CLR);
|
||||
writel_relaxed(lic->cpu_ier[i],
|
||||
ictlr + ICTLR_CPU_IER_SET);
|
||||
writel_relaxed(lic->cop_iep[i],
|
||||
ictlr + ICTLR_COP_IEP_CLASS);
|
||||
writel_relaxed(~0ul, ictlr + ICTLR_COP_IER_CLR);
|
||||
writel_relaxed(lic->cop_ier[i],
|
||||
ictlr + ICTLR_COP_IER_SET);
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
static struct syscore_ops tegra_ictlr_syscore_ops = {
|
||||
.suspend = tegra_ictlr_suspend,
|
||||
.resume = tegra_ictlr_resume,
|
||||
};
|
||||
|
||||
static void tegra_ictlr_syscore_init(void)
|
||||
{
|
||||
register_syscore_ops(&tegra_ictlr_syscore_ops);
|
||||
}
|
||||
#else
|
||||
#define tegra_set_wake NULL
|
||||
static inline void tegra_ictlr_syscore_init(void) {}
|
||||
#endif
|
||||
|
||||
static struct irq_chip tegra_ictlr_chip = {
|
||||
.name = "LIC",
|
||||
.irq_eoi = tegra_eoi,
|
||||
.irq_mask = tegra_mask,
|
||||
.irq_unmask = tegra_unmask,
|
||||
.irq_retrigger = tegra_retrigger,
|
||||
.irq_set_wake = tegra_set_wake,
|
||||
.flags = IRQCHIP_MASK_ON_SUSPEND,
|
||||
#ifdef CONFIG_SMP
|
||||
.irq_set_affinity = irq_chip_set_affinity_parent,
|
||||
#endif
|
||||
};
|
||||
|
||||
static int tegra_ictlr_domain_xlate(struct irq_domain *domain,
|
||||
struct device_node *controller,
|
||||
const u32 *intspec,
|
||||
unsigned int intsize,
|
||||
unsigned long *out_hwirq,
|
||||
unsigned int *out_type)
|
||||
{
|
||||
if (domain->of_node != controller)
|
||||
return -EINVAL; /* Shouldn't happen, really... */
|
||||
if (intsize != 3)
|
||||
return -EINVAL; /* Not GIC compliant */
|
||||
if (intspec[0] != GIC_SPI)
|
||||
return -EINVAL; /* No PPI should point to this domain */
|
||||
|
||||
*out_hwirq = intspec[1];
|
||||
*out_type = intspec[2];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_ictlr_domain_alloc(struct irq_domain *domain,
|
||||
unsigned int virq,
|
||||
unsigned int nr_irqs, void *data)
|
||||
{
|
||||
struct of_phandle_args *args = data;
|
||||
struct of_phandle_args parent_args;
|
||||
struct tegra_ictlr_info *info = domain->host_data;
|
||||
irq_hw_number_t hwirq;
|
||||
unsigned int i;
|
||||
|
||||
if (args->args_count != 3)
|
||||
return -EINVAL; /* Not GIC compliant */
|
||||
if (args->args[0] != GIC_SPI)
|
||||
return -EINVAL; /* No PPI should point to this domain */
|
||||
|
||||
hwirq = args->args[1];
|
||||
if (hwirq >= (num_ictlrs * 32))
|
||||
return -EINVAL;
|
||||
|
||||
for (i = 0; i < nr_irqs; i++) {
|
||||
int ictlr = (hwirq + i) / 32;
|
||||
|
||||
irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
|
||||
&tegra_ictlr_chip,
|
||||
&info->base[ictlr]);
|
||||
}
|
||||
|
||||
parent_args = *args;
|
||||
parent_args.np = domain->parent->of_node;
|
||||
return irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &parent_args);
|
||||
}
|
||||
|
||||
static void tegra_ictlr_domain_free(struct irq_domain *domain,
|
||||
unsigned int virq,
|
||||
unsigned int nr_irqs)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct irq_domain_ops tegra_ictlr_domain_ops = {
|
||||
.xlate = tegra_ictlr_domain_xlate,
|
||||
.alloc = tegra_ictlr_domain_alloc,
|
||||
.free = tegra_ictlr_domain_free,
|
||||
};
|
||||
|
||||
static int __init tegra_ictlr_init(struct device_node *node,
|
||||
struct device_node *parent)
|
||||
{
|
||||
struct irq_domain *parent_domain, *domain;
|
||||
const struct of_device_id *match;
|
||||
const struct tegra_ictlr_soc *soc;
|
||||
unsigned int i;
|
||||
int err;
|
||||
|
||||
if (!parent) {
|
||||
pr_err("%s: no parent, giving up\n", node->full_name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
parent_domain = irq_find_host(parent);
|
||||
if (!parent_domain) {
|
||||
pr_err("%s: unable to obtain parent domain\n", node->full_name);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
match = of_match_node(ictlr_matches, node);
|
||||
if (!match) /* Should never happen... */
|
||||
return -ENODEV;
|
||||
|
||||
soc = match->data;
|
||||
|
||||
lic = kzalloc(sizeof(*lic), GFP_KERNEL);
|
||||
if (!lic)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < TEGRA_MAX_NUM_ICTLRS; i++) {
|
||||
void __iomem *base;
|
||||
|
||||
base = of_iomap(node, i);
|
||||
if (!base)
|
||||
break;
|
||||
|
||||
lic->base[i] = base;
|
||||
|
||||
/* Disable all interrupts */
|
||||
writel_relaxed(~0UL, base + ICTLR_CPU_IER_CLR);
|
||||
/* All interrupts target IRQ */
|
||||
writel_relaxed(0, base + ICTLR_CPU_IEP_CLASS);
|
||||
|
||||
num_ictlrs++;
|
||||
}
|
||||
|
||||
if (!num_ictlrs) {
|
||||
pr_err("%s: no valid regions, giving up\n", node->full_name);
|
||||
err = -ENOMEM;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
WARN(num_ictlrs != soc->num_ictlrs,
|
||||
"%s: Found %u interrupt controllers in DT; expected %u.\n",
|
||||
node->full_name, num_ictlrs, soc->num_ictlrs);
|
||||
|
||||
|
||||
domain = irq_domain_add_hierarchy(parent_domain, 0, num_ictlrs * 32,
|
||||
node, &tegra_ictlr_domain_ops,
|
||||
lic);
|
||||
if (!domain) {
|
||||
pr_err("%s: failed to allocated domain\n", node->full_name);
|
||||
err = -ENOMEM;
|
||||
goto out_unmap;
|
||||
}
|
||||
|
||||
tegra_ictlr_syscore_init();
|
||||
|
||||
pr_info("%s: %d interrupts forwarded to %s\n",
|
||||
node->full_name, num_ictlrs * 32, parent->full_name);
|
||||
|
||||
return 0;
|
||||
|
||||
out_unmap:
|
||||
for (i = 0; i < num_ictlrs; i++)
|
||||
iounmap(lic->base[i]);
|
||||
out_free:
|
||||
kfree(lic);
|
||||
return err;
|
||||
}
|
||||
|
||||
IRQCHIP_DECLARE(tegra20_ictlr, "nvidia,tegra20-ictlr", tegra_ictlr_init);
|
||||
IRQCHIP_DECLARE(tegra30_ictlr, "nvidia,tegra30-ictlr", tegra_ictlr_init);
|
||||
IRQCHIP_DECLARE(tegra210_ictlr, "nvidia,tegra210-ictlr", tegra_ictlr_init);
|
||||
Loading…
Reference in a new issue