forked from whatawurst/android_kernel_sony_msm8998
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
irqchip: bcm7120-l2: Add Broadcom BCM7120-style Level 2 interrupt con…
…troller This patch adds support for the Level-2 interrupt controller hardware found in Broadcom Set Top Box System-on-a-Chip devices. This interrupt controller is implemented using a single enable register. This interrupt controller is always present on the platforms supported by the irq-brcmstb-l2 driver, hence the reason why both are compiled using the same Kconfig symbol. [jac] removed the following warning: drivers/irqchip/irq-bcm7120-l2.c: In function 'bcm7120_l2_intc_irq_handle': drivers/irqchip/irq-bcm7120-l2.c:49:27: warning: unused variable 'gc' [-Wunused-variable] Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> Link: https://lkml.kernel.org/r/1410309862-27784-2-git-send-email-f.fainelli@gmail.com Signed-off-by: Jason Cooper <jason@lakedaemon.net>
- Loading branch information
Showing
2 changed files
with
221 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
/* | ||
* Broadcom BCM7120 style Level 2 interrupt controller driver | ||
* | ||
* Copyright (C) 2014 Broadcom Corporation | ||
* | ||
* 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. | ||
*/ | ||
|
||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
|
||
#include <linux/init.h> | ||
#include <linux/slab.h> | ||
#include <linux/module.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/of.h> | ||
#include <linux/of_irq.h> | ||
#include <linux/of_address.h> | ||
#include <linux/of_platform.h> | ||
#include <linux/interrupt.h> | ||
#include <linux/irq.h> | ||
#include <linux/io.h> | ||
#include <linux/irqdomain.h> | ||
#include <linux/reboot.h> | ||
#include <linux/irqchip/chained_irq.h> | ||
|
||
#include "irqchip.h" | ||
|
||
#include <asm/mach/irq.h> | ||
|
||
/* Register offset in the L2 interrupt controller */ | ||
#define IRQEN 0x00 | ||
#define IRQSTAT 0x04 | ||
|
||
struct bcm7120_l2_intc_data { | ||
void __iomem *base; | ||
struct irq_domain *domain; | ||
bool can_wake; | ||
u32 irq_fwd_mask; | ||
u32 irq_map_mask; | ||
u32 saved_mask; | ||
}; | ||
|
||
static void bcm7120_l2_intc_irq_handle(unsigned int irq, struct irq_desc *desc) | ||
{ | ||
struct bcm7120_l2_intc_data *b = irq_desc_get_handler_data(desc); | ||
struct irq_chip *chip = irq_desc_get_chip(desc); | ||
u32 status; | ||
|
||
chained_irq_enter(chip, desc); | ||
|
||
status = __raw_readl(b->base + IRQSTAT); | ||
|
||
if (status == 0) { | ||
do_bad_IRQ(irq, desc); | ||
goto out; | ||
} | ||
|
||
do { | ||
irq = ffs(status) - 1; | ||
status &= ~(1 << irq); | ||
generic_handle_irq(irq_find_mapping(b->domain, irq)); | ||
} while (status); | ||
|
||
out: | ||
chained_irq_exit(chip, desc); | ||
} | ||
|
||
static void bcm7120_l2_intc_suspend(struct irq_data *d) | ||
{ | ||
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); | ||
struct bcm7120_l2_intc_data *b = gc->private; | ||
u32 reg; | ||
|
||
irq_gc_lock(gc); | ||
/* Save the current mask and the interrupt forward mask */ | ||
b->saved_mask = __raw_readl(b->base) | b->irq_fwd_mask; | ||
if (b->can_wake) { | ||
reg = b->saved_mask | gc->wake_active; | ||
__raw_writel(reg, b->base); | ||
} | ||
irq_gc_unlock(gc); | ||
} | ||
|
||
static void bcm7120_l2_intc_resume(struct irq_data *d) | ||
{ | ||
struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); | ||
struct bcm7120_l2_intc_data *b = gc->private; | ||
|
||
/* Restore the saved mask */ | ||
irq_gc_lock(gc); | ||
__raw_writel(b->saved_mask, b->base); | ||
irq_gc_unlock(gc); | ||
} | ||
|
||
static int bcm7120_l2_intc_init_one(struct device_node *dn, | ||
struct bcm7120_l2_intc_data *data, | ||
int irq, const __be32 *map_mask) | ||
{ | ||
int parent_irq; | ||
|
||
parent_irq = irq_of_parse_and_map(dn, irq); | ||
if (parent_irq < 0) { | ||
pr_err("failed to map interrupt %d\n", irq); | ||
return parent_irq; | ||
} | ||
|
||
data->irq_map_mask |= be32_to_cpup(map_mask + irq); | ||
|
||
irq_set_handler_data(parent_irq, data); | ||
irq_set_chained_handler(parent_irq, bcm7120_l2_intc_irq_handle); | ||
|
||
return 0; | ||
} | ||
|
||
int __init bcm7120_l2_intc_of_init(struct device_node *dn, | ||
struct device_node *parent) | ||
{ | ||
unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN; | ||
struct bcm7120_l2_intc_data *data; | ||
struct irq_chip_generic *gc; | ||
struct irq_chip_type *ct; | ||
const __be32 *map_mask; | ||
int num_parent_irqs; | ||
int ret = 0, len, irq; | ||
|
||
data = kzalloc(sizeof(*data), GFP_KERNEL); | ||
if (!data) | ||
return -ENOMEM; | ||
|
||
data->base = of_iomap(dn, 0); | ||
if (!data->base) { | ||
pr_err("failed to remap intc L2 registers\n"); | ||
ret = -ENOMEM; | ||
goto out_free; | ||
} | ||
|
||
if (of_property_read_u32(dn, "brcm,int-fwd-mask", &data->irq_fwd_mask)) | ||
data->irq_fwd_mask = 0; | ||
|
||
/* Enable all interrupt specified in the interrupt forward mask and have | ||
* the other disabled | ||
*/ | ||
__raw_writel(data->irq_fwd_mask, data->base + IRQEN); | ||
|
||
num_parent_irqs = of_irq_count(dn); | ||
if (num_parent_irqs <= 0) { | ||
pr_err("invalid number of parent interrupts\n"); | ||
ret = -ENOMEM; | ||
goto out_unmap; | ||
} | ||
|
||
map_mask = of_get_property(dn, "brcm,int-map-mask", &len); | ||
if (!map_mask || (len != (sizeof(*map_mask) * num_parent_irqs))) { | ||
pr_err("invalid brcm,int-map-mask property\n"); | ||
ret = -EINVAL; | ||
goto out_unmap; | ||
} | ||
|
||
for (irq = 0; irq < num_parent_irqs; irq++) { | ||
ret = bcm7120_l2_intc_init_one(dn, data, irq, map_mask); | ||
if (ret) | ||
goto out_unmap; | ||
} | ||
|
||
data->domain = irq_domain_add_linear(dn, 32, | ||
&irq_generic_chip_ops, NULL); | ||
if (!data->domain) { | ||
ret = -ENOMEM; | ||
goto out_unmap; | ||
} | ||
|
||
ret = irq_alloc_domain_generic_chips(data->domain, 32, 1, | ||
dn->full_name, handle_level_irq, clr, 0, | ||
IRQ_GC_INIT_MASK_CACHE); | ||
if (ret) { | ||
pr_err("failed to allocate generic irq chip\n"); | ||
goto out_free_domain; | ||
} | ||
|
||
gc = irq_get_domain_generic_chip(data->domain, 0); | ||
gc->unused = 0xfffffff & ~data->irq_map_mask; | ||
gc->reg_base = data->base; | ||
gc->private = data; | ||
ct = gc->chip_types; | ||
|
||
ct->regs.mask = IRQEN; | ||
ct->chip.irq_mask = irq_gc_mask_clr_bit; | ||
ct->chip.irq_unmask = irq_gc_mask_set_bit; | ||
ct->chip.irq_ack = irq_gc_noop; | ||
ct->chip.irq_suspend = bcm7120_l2_intc_suspend; | ||
ct->chip.irq_resume = bcm7120_l2_intc_resume; | ||
|
||
if (of_property_read_bool(dn, "brcm,irq-can-wake")) { | ||
data->can_wake = true; | ||
/* This IRQ chip can wake the system, set all relevant child | ||
* interupts in wake_enabled mask | ||
*/ | ||
gc->wake_enabled = 0xffffffff; | ||
gc->wake_enabled &= ~gc->unused; | ||
ct->chip.irq_set_wake = irq_gc_set_wake; | ||
} | ||
|
||
pr_info("registered BCM7120 L2 intc (mem: 0x%p, parent IRQ(s): %d)\n", | ||
data->base, num_parent_irqs); | ||
|
||
return 0; | ||
|
||
out_free_domain: | ||
irq_domain_remove(data->domain); | ||
out_unmap: | ||
iounmap(data->base); | ||
out_free: | ||
kfree(data); | ||
return ret; | ||
} | ||
IRQCHIP_DECLARE(brcmstb_l2_intc, "brcm,bcm7120-l2-intc", | ||
bcm7120_l2_intc_of_init); |