summaryrefslogtreecommitdiffstats
path: root/drivers/sh/intc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/sh/intc.c')
-rw-r--r--drivers/sh/intc.c404
1 files changed, 338 insertions, 66 deletions
diff --git a/drivers/sh/intc.c b/drivers/sh/intc.c
index a27dcb4254c7..c81fe23db7f7 100644
--- a/drivers/sh/intc.c
+++ b/drivers/sh/intc.c
@@ -35,6 +35,7 @@
#include <linux/seq_file.h>
#include <linux/radix-tree.h>
#include <linux/mutex.h>
+#include <linux/rcupdate.h>
#include <asm/sizes.h>
#define _INTC_MK(fn, mode, addr_e, addr_d, width, shift) \
@@ -64,11 +65,19 @@ struct intc_map_entry {
struct intc_desc_int *desc;
};
+struct intc_subgroup_entry {
+ unsigned int pirq;
+ intc_enum enum_id;
+ unsigned long handle;
+};
+
struct intc_desc_int {
struct list_head list;
struct sys_device sysdev;
struct radix_tree_root tree;
pm_message_t state;
+ spinlock_t lock;
+ unsigned int index;
unsigned long *reg;
#ifdef CONFIG_SMP
unsigned long *smp;
@@ -84,6 +93,7 @@ struct intc_desc_int {
};
static LIST_HEAD(intc_list);
+static unsigned int nr_intc_controllers;
/*
* The intc_irq_map provides a global map of bound IRQ vectors for a
@@ -99,7 +109,7 @@ static LIST_HEAD(intc_list);
static DECLARE_BITMAP(intc_irq_map, NR_IRQS);
static struct intc_map_entry intc_irq_xlate[NR_IRQS];
static DEFINE_SPINLOCK(vector_lock);
-static DEFINE_MUTEX(irq_xlate_mutex);
+static DEFINE_SPINLOCK(xlate_lock);
#ifdef CONFIG_SMP
#define IS_SMP(x) x.smp
@@ -118,12 +128,39 @@ static unsigned long ack_handle[NR_IRQS];
static unsigned long dist_handle[NR_IRQS];
#endif
+struct intc_virq_list {
+ unsigned int irq;
+ struct intc_virq_list *next;
+};
+
+#define for_each_virq(entry, head) \
+ for (entry = head; entry; entry = entry->next)
+
static inline struct intc_desc_int *get_intc_desc(unsigned int irq)
{
struct irq_chip *chip = get_irq_chip(irq);
+
return container_of(chip, struct intc_desc_int, chip);
}
+static void intc_redirect_irq(unsigned int irq, struct irq_desc *desc)
+{
+ generic_handle_irq((unsigned int)get_irq_data(irq));
+}
+
+static inline void activate_irq(int irq)
+{
+#ifdef CONFIG_ARM
+ /* ARM requires an extra step to clear IRQ_NOREQUEST, which it
+ * sets on behalf of every irq_chip. Also sets IRQ_NOPROBE.
+ */
+ set_irq_flags(irq, IRQF_VALID);
+#else
+ /* same effect on other architectures */
+ set_irq_noprobe(irq);
+#endif
+}
+
static unsigned long intc_phys_to_virt(struct intc_desc_int *d,
unsigned long address)
{
@@ -177,56 +214,103 @@ static inline unsigned int set_field(unsigned int value,
return value;
}
-static void write_8(unsigned long addr, unsigned long h, unsigned long data)
+static inline unsigned long get_field(unsigned int value, unsigned int handle)
+{
+ unsigned int width = _INTC_WIDTH(handle);
+ unsigned int shift = _INTC_SHIFT(handle);
+ unsigned int mask = ((1 << width) - 1) << shift;
+
+ return (value & mask) >> shift;
+}
+
+static unsigned long test_8(unsigned long addr, unsigned long h,
+ unsigned long ignore)
+{
+ return get_field(__raw_readb(addr), h);
+}
+
+static unsigned long test_16(unsigned long addr, unsigned long h,
+ unsigned long ignore)
+{
+ return get_field(__raw_readw(addr), h);
+}
+
+static unsigned long test_32(unsigned long addr, unsigned long h,
+ unsigned long ignore)
+{
+ return get_field(__raw_readl(addr), h);
+}
+
+static unsigned long write_8(unsigned long addr, unsigned long h,
+ unsigned long data)
{
__raw_writeb(set_field(0, data, h), addr);
(void)__raw_readb(addr); /* Defeat write posting */
+ return 0;
}
-static void write_16(unsigned long addr, unsigned long h, unsigned long data)
+static unsigned long write_16(unsigned long addr, unsigned long h,
+ unsigned long data)
{
__raw_writew(set_field(0, data, h), addr);
(void)__raw_readw(addr); /* Defeat write posting */
+ return 0;
}
-static void write_32(unsigned long addr, unsigned long h, unsigned long data)
+static unsigned long write_32(unsigned long addr, unsigned long h,
+ unsigned long data)
{
__raw_writel(set_field(0, data, h), addr);
(void)__raw_readl(addr); /* Defeat write posting */
+ return 0;
}
-static void modify_8(unsigned long addr, unsigned long h, unsigned long data)
+static unsigned long modify_8(unsigned long addr, unsigned long h,
+ unsigned long data)
{
unsigned long flags;
local_irq_save(flags);
__raw_writeb(set_field(__raw_readb(addr), data, h), addr);
(void)__raw_readb(addr); /* Defeat write posting */
local_irq_restore(flags);
+ return 0;
}
-static void modify_16(unsigned long addr, unsigned long h, unsigned long data)
+static unsigned long modify_16(unsigned long addr, unsigned long h,
+ unsigned long data)
{
unsigned long flags;
local_irq_save(flags);
__raw_writew(set_field(__raw_readw(addr), data, h), addr);
(void)__raw_readw(addr); /* Defeat write posting */
local_irq_restore(flags);
+ return 0;
}
-static void modify_32(unsigned long addr, unsigned long h, unsigned long data)
+static unsigned long modify_32(unsigned long addr, unsigned long h,
+ unsigned long data)
{
unsigned long flags;
local_irq_save(flags);
__raw_writel(set_field(__raw_readl(addr), data, h), addr);
(void)__raw_readl(addr); /* Defeat write posting */
local_irq_restore(flags);
+ return 0;
}
-enum { REG_FN_ERR = 0, REG_FN_WRITE_BASE = 1, REG_FN_MODIFY_BASE = 5 };
+enum {
+ REG_FN_ERR = 0,
+ REG_FN_TEST_BASE = 1,
+ REG_FN_WRITE_BASE = 5,
+ REG_FN_MODIFY_BASE = 9
+};
-static void (*intc_reg_fns[])(unsigned long addr,
- unsigned long h,
- unsigned long data) = {
+static unsigned long (*intc_reg_fns[])(unsigned long addr,
+ unsigned long h,
+ unsigned long data) = {
+ [REG_FN_TEST_BASE + 0] = test_8,
+ [REG_FN_TEST_BASE + 1] = test_16,
+ [REG_FN_TEST_BASE + 3] = test_32,
[REG_FN_WRITE_BASE + 0] = write_8,
[REG_FN_WRITE_BASE + 1] = write_16,
[REG_FN_WRITE_BASE + 3] = write_32,
@@ -242,42 +326,42 @@ enum { MODE_ENABLE_REG = 0, /* Bit(s) set -> interrupt enabled */
MODE_PCLR_REG, /* Above plus all bits set to disable interrupt */
};
-static void intc_mode_field(unsigned long addr,
- unsigned long handle,
- void (*fn)(unsigned long,
- unsigned long,
- unsigned long),
- unsigned int irq)
+static unsigned long intc_mode_field(unsigned long addr,
+ unsigned long handle,
+ unsigned long (*fn)(unsigned long,
+ unsigned long,
+ unsigned long),
+ unsigned int irq)
{
- fn(addr, handle, ((1 << _INTC_WIDTH(handle)) - 1));
+ return fn(addr, handle, ((1 << _INTC_WIDTH(handle)) - 1));
}
-static void intc_mode_zero(unsigned long addr,
- unsigned long handle,
- void (*fn)(unsigned long,
- unsigned long,
- unsigned long),
- unsigned int irq)
+static unsigned long intc_mode_zero(unsigned long addr,
+ unsigned long handle,
+ unsigned long (*fn)(unsigned long,
+ unsigned long,
+ unsigned long),
+ unsigned int irq)
{
- fn(addr, handle, 0);
+ return fn(addr, handle, 0);
}
-static void intc_mode_prio(unsigned long addr,
- unsigned long handle,
- void (*fn)(unsigned long,
- unsigned long,
- unsigned long),
- unsigned int irq)
+static unsigned long intc_mode_prio(unsigned long addr,
+ unsigned long handle,
+ unsigned long (*fn)(unsigned long,
+ unsigned long,
+ unsigned long),
+ unsigned int irq)
{
- fn(addr, handle, intc_prio_level[irq]);
+ return fn(addr, handle, intc_prio_level[irq]);
}
-static void (*intc_enable_fns[])(unsigned long addr,
- unsigned long handle,
- void (*fn)(unsigned long,
- unsigned long,
- unsigned long),
- unsigned int irq) = {
+static unsigned long (*intc_enable_fns[])(unsigned long addr,
+ unsigned long handle,
+ unsigned long (*fn)(unsigned long,
+ unsigned long,
+ unsigned long),
+ unsigned int irq) = {
[MODE_ENABLE_REG] = intc_mode_field,
[MODE_MASK_REG] = intc_mode_zero,
[MODE_DUAL_REG] = intc_mode_field,
@@ -285,9 +369,9 @@ static void (*intc_enable_fns[])(unsigned long addr,
[MODE_PCLR_REG] = intc_mode_prio,
};
-static void (*intc_disable_fns[])(unsigned long addr,
+static unsigned long (*intc_disable_fns[])(unsigned long addr,
unsigned long handle,
- void (*fn)(unsigned long,
+ unsigned long (*fn)(unsigned long,
unsigned long,
unsigned long),
unsigned int irq) = {
@@ -421,12 +505,13 @@ static void intc_disable(unsigned int irq)
}
}
-static void (*intc_enable_noprio_fns[])(unsigned long addr,
- unsigned long handle,
- void (*fn)(unsigned long,
- unsigned long,
- unsigned long),
- unsigned int irq) = {
+static unsigned long
+(*intc_enable_noprio_fns[])(unsigned long addr,
+ unsigned long handle,
+ unsigned long (*fn)(unsigned long,
+ unsigned long,
+ unsigned long),
+ unsigned int irq) = {
[MODE_ENABLE_REG] = intc_mode_field,
[MODE_MASK_REG] = intc_mode_zero,
[MODE_DUAL_REG] = intc_mode_field,
@@ -439,8 +524,9 @@ static void intc_enable_disable(struct intc_desc_int *d,
{
unsigned long addr;
unsigned int cpu;
- void (*fn)(unsigned long, unsigned long,
- void (*)(unsigned long, unsigned long, unsigned long),
+ unsigned long (*fn)(unsigned long, unsigned long,
+ unsigned long (*)(unsigned long, unsigned long,
+ unsigned long),
unsigned int);
if (do_enable) {
@@ -861,6 +947,186 @@ unsigned int intc_irq_lookup(const char *chipname, intc_enum enum_id)
}
EXPORT_SYMBOL_GPL(intc_irq_lookup);
+static int add_virq_to_pirq(unsigned int irq, unsigned int virq)
+{
+ struct intc_virq_list **last, *entry;
+ struct irq_desc *desc = irq_to_desc(irq);
+
+ /* scan for duplicates */
+ last = (struct intc_virq_list **)&desc->handler_data;
+ for_each_virq(entry, desc->handler_data) {
+ if (entry->irq == virq)
+ return 0;
+ last = &entry->next;
+ }
+
+ entry = kzalloc(sizeof(struct intc_virq_list), GFP_ATOMIC);
+ if (!entry) {
+ pr_err("can't allocate VIRQ mapping for %d\n", virq);
+ return -ENOMEM;
+ }
+
+ entry->irq = virq;
+
+ *last = entry;
+
+ return 0;
+}
+
+static void intc_virq_handler(unsigned int irq, struct irq_desc *desc)
+{
+ struct intc_virq_list *entry, *vlist = get_irq_data(irq);
+ struct intc_desc_int *d = get_intc_desc(irq);
+
+ desc->chip->mask_ack(irq);
+
+ for_each_virq(entry, vlist) {
+ unsigned long addr, handle;
+
+ handle = (unsigned long)get_irq_data(entry->irq);
+ addr = INTC_REG(d, _INTC_ADDR_E(handle), 0);
+
+ if (intc_reg_fns[_INTC_FN(handle)](addr, handle, 0))
+ generic_handle_irq(entry->irq);
+ }
+
+ desc->chip->unmask(irq);
+}
+
+static unsigned long __init intc_subgroup_data(struct intc_subgroup *subgroup,
+ struct intc_desc_int *d,
+ unsigned int index)
+{
+ unsigned int fn = REG_FN_TEST_BASE + (subgroup->reg_width >> 3) - 1;
+
+ return _INTC_MK(fn, MODE_ENABLE_REG, intc_get_reg(d, subgroup->reg),
+ 0, 1, (subgroup->reg_width - 1) - index);
+}
+
+#define INTC_TAG_VIRQ_NEEDS_ALLOC 0
+
+static void __init intc_subgroup_init_one(struct intc_desc *desc,
+ struct intc_desc_int *d,
+ struct intc_subgroup *subgroup)
+{
+ struct intc_map_entry *mapped;
+ unsigned int pirq;
+ unsigned long flags;
+ int i;
+
+ mapped = radix_tree_lookup(&d->tree, subgroup->parent_id);
+ if (!mapped) {
+ WARN_ON(1);
+ return;
+ }
+
+ pirq = mapped - intc_irq_xlate;
+
+ spin_lock_irqsave(&d->lock, flags);
+
+ for (i = 0; i < ARRAY_SIZE(subgroup->enum_ids); i++) {
+ struct intc_subgroup_entry *entry;
+ int err;
+
+ if (!subgroup->enum_ids[i])
+ continue;
+
+ entry = kmalloc(sizeof(*entry), GFP_NOWAIT);
+ if (!entry)
+ break;
+
+ entry->pirq = pirq;
+ entry->enum_id = subgroup->enum_ids[i];
+ entry->handle = intc_subgroup_data(subgroup, d, i);
+
+ err = radix_tree_insert(&d->tree, entry->enum_id, entry);
+ if (unlikely(err < 0))
+ break;
+
+ radix_tree_tag_set(&d->tree, entry->enum_id,
+ INTC_TAG_VIRQ_NEEDS_ALLOC);
+ }
+
+ spin_unlock_irqrestore(&d->lock, flags);
+}
+
+static void __init intc_subgroup_init(struct intc_desc *desc,
+ struct intc_desc_int *d)
+{
+ int i;
+
+ if (!desc->hw.subgroups)
+ return;
+
+ for (i = 0; i < desc->hw.nr_subgroups; i++)
+ intc_subgroup_init_one(desc, d, desc->hw.subgroups + i);
+}
+
+static void __init intc_subgroup_map(struct intc_desc_int *d)
+{
+ struct intc_subgroup_entry *entries[32];
+ unsigned long flags;
+ unsigned int nr_found;
+ int i;
+
+ spin_lock_irqsave(&d->lock, flags);
+
+restart:
+ nr_found = radix_tree_gang_lookup_tag_slot(&d->tree,
+ (void ***)entries, 0, ARRAY_SIZE(entries),
+ INTC_TAG_VIRQ_NEEDS_ALLOC);
+
+ for (i = 0; i < nr_found; i++) {
+ struct intc_subgroup_entry *entry;
+ int irq;
+
+ entry = radix_tree_deref_slot((void **)entries[i]);
+ if (unlikely(!entry))
+ continue;
+ if (unlikely(entry == RADIX_TREE_RETRY))
+ goto restart;
+
+ irq = create_irq();
+ if (unlikely(irq < 0)) {
+ pr_err("no more free IRQs, bailing..\n");
+ break;
+ }
+
+ pr_info("Setting up a chained VIRQ from %d -> %d\n",
+ irq, entry->pirq);
+
+ spin_lock(&xlate_lock);
+ intc_irq_xlate[irq].desc = d;
+ intc_irq_xlate[irq].enum_id = entry->enum_id;
+ spin_unlock(&xlate_lock);
+
+ set_irq_chip_and_handler_name(irq, get_irq_chip(entry->pirq),
+ handle_simple_irq, "virq");
+ set_irq_chip_data(irq, get_irq_chip_data(entry->pirq));
+
+ set_irq_data(irq, (void *)entry->handle);
+
+ set_irq_chained_handler(entry->pirq, intc_virq_handler);
+ add_virq_to_pirq(entry->pirq, irq);
+
+ radix_tree_tag_clear(&d->tree, entry->enum_id,
+ INTC_TAG_VIRQ_NEEDS_ALLOC);
+ radix_tree_replace_slot((void **)entries[i],
+ &intc_irq_xlate[irq]);
+ }
+
+ spin_unlock_irqrestore(&d->lock, flags);
+}
+
+void __init intc_finalize(void)
+{
+ struct intc_desc_int *d;
+
+ list_for_each_entry(d, &intc_list, list)
+ if (radix_tree_tagged(&d->tree, INTC_TAG_VIRQ_NEEDS_ALLOC))
+ intc_subgroup_map(d);
+}
+
static void __init intc_register_irq(struct intc_desc *desc,
struct intc_desc_int *d,
intc_enum enum_id,
@@ -868,6 +1134,7 @@ static void __init intc_register_irq(struct intc_desc *desc,
{
struct intc_handle_int *hp;
unsigned int data[2], primary;
+ unsigned long flags;
/*
* Register the IRQ position with the global IRQ map, then insert
@@ -875,9 +1142,9 @@ static void __init intc_register_irq(struct intc_desc *desc,
*/
set_bit(irq, intc_irq_map);
- mutex_lock(&irq_xlate_mutex);
+ spin_lock_irqsave(&xlate_lock, flags);
radix_tree_insert(&d->tree, enum_id, &intc_irq_xlate[irq]);
- mutex_unlock(&irq_xlate_mutex);
+ spin_unlock_irqrestore(&xlate_lock, flags);
/*
* Prefer single interrupt source bitmap over other combinations:
@@ -957,9 +1224,7 @@ static void __init intc_register_irq(struct intc_desc *desc,
dist_handle[irq] = intc_dist_data(desc, d, enum_id);
#endif
-#ifdef CONFIG_ARM
- set_irq_flags(irq, IRQF_VALID); /* Enable IRQ on ARM systems */
-#endif
+ activate_irq(irq);
}
static unsigned int __init save_reg(struct intc_desc_int *d,
@@ -980,11 +1245,6 @@ static unsigned int __init save_reg(struct intc_desc_int *d,
return 0;
}
-static void intc_redirect_irq(unsigned int irq, struct irq_desc *desc)
-{
- generic_handle_irq((unsigned int)get_irq_data(irq));
-}
-
int __init register_intc_controller(struct intc_desc *desc)
{
unsigned int i, k, smp;
@@ -1000,7 +1260,11 @@ int __init register_intc_controller(struct intc_desc *desc)
goto err0;
INIT_LIST_HEAD(&d->list);
- list_add(&d->list, &intc_list);
+ list_add_tail(&d->list, &intc_list);
+
+ spin_lock_init(&d->lock);
+
+ d->index = nr_intc_controllers;
if (desc->num_resources) {
d->nr_windows = desc->num_resources;
@@ -1029,6 +1293,7 @@ int __init register_intc_controller(struct intc_desc *desc)
d->nr_reg += hw->prio_regs ? hw->nr_prio_regs * 2 : 0;
d->nr_reg += hw->sense_regs ? hw->nr_sense_regs : 0;
d->nr_reg += hw->ack_regs ? hw->nr_ack_regs : 0;
+ d->nr_reg += hw->subgroups ? hw->nr_subgroups : 0;
d->reg = kzalloc(d->nr_reg * sizeof(*d->reg), GFP_NOWAIT);
if (!d->reg)
@@ -1075,6 +1340,11 @@ int __init register_intc_controller(struct intc_desc *desc)
k += save_reg(d, k, hw->sense_regs[i].reg, 0);
}
+ if (hw->subgroups)
+ for (i = 0; i < hw->nr_subgroups; i++)
+ if (hw->subgroups[i].reg)
+ k+= save_reg(d, k, hw->subgroups[i].reg, 0);
+
d->chip.name = desc->name;
d->chip.mask = intc_disable;
d->chip.unmask = intc_enable;
@@ -1109,6 +1379,7 @@ int __init register_intc_controller(struct intc_desc *desc)
for (i = 0; i < hw->nr_vectors; i++) {
struct intc_vect *vect = hw->vectors + i;
unsigned int irq = evt2irq(vect->vect);
+ unsigned long flags;
struct irq_desc *irq_desc;
if (!vect->enum_id)
@@ -1120,8 +1391,10 @@ int __init register_intc_controller(struct intc_desc *desc)
continue;
}
+ spin_lock_irqsave(&xlate_lock, flags);
intc_irq_xlate[irq].enum_id = vect->enum_id;
intc_irq_xlate[irq].desc = d;
+ spin_unlock_irqrestore(&xlate_lock, flags);
intc_register_irq(desc, d, vect->enum_id, irq);
@@ -1152,10 +1425,14 @@ int __init register_intc_controller(struct intc_desc *desc)
}
}
+ intc_subgroup_init(desc, d);
+
/* enable bits matching force_enable after registering irqs */
if (desc->force_enable)
intc_enable_disable_enum(desc, d, desc->force_enable, 1);
+ nr_intc_controllers++;
+
return 0;
err5:
kfree(d->prio);
@@ -1353,7 +1630,6 @@ static int __init register_intc_sysdevs(void)
{
struct intc_desc_int *d;
int error;
- int id = 0;
error = sysdev_class_register(&intc_sysdev_class);
#ifdef CONFIG_INTC_USERIMASK
@@ -1363,7 +1639,7 @@ static int __init register_intc_sysdevs(void)
#endif
if (!error) {
list_for_each_entry(d, &intc_list, list) {
- d->sysdev.id = id;
+ d->sysdev.id = d->index;
d->sysdev.cls = &intc_sysdev_class;
error = sysdev_register(&d->sysdev);
if (error == 0)
@@ -1371,8 +1647,6 @@ static int __init register_intc_sysdevs(void)
&attr_name);
if (error)
break;
-
- id++;
}
}
@@ -1422,9 +1696,7 @@ out_unlock:
if (irq > 0) {
dynamic_irq_init(irq);
-#ifdef CONFIG_ARM
- set_irq_flags(irq, IRQF_VALID); /* Enable IRQ on ARM systems */
-#endif
+ activate_irq(irq);
}
return irq;
OpenPOWER on IntegriCloud