linux/drivers/irqchip/irq-mmp.c
Linus Torvalds dfc25e4503 ARM: SoC: cleanups for 3.15
These cleanup patches are mainly move stuff around and should all
 be harmless. They are mainly split out so that other branches can
 be based on top to avoid conflicts.
 
 Notable changes are:
 
 * We finally remove all mach/timex.h, after CLOCK_TICK_RATE is no
   longer used. (Uwe Kleine-König)
 * The Qualcomm MSM platform is split out into legacy mach-msm and
   new-style mach-qcom, to allow easier maintainance of the new
   hardware support without regressions. (Kumar Gala)
 * A rework of some of the Kconfig logic to simplify multiplatform
   support (Rob Herring)
 * Samsung Exynos gets closer to supporting multiplatform (Sachin
   Kamat and others)
 * mach-bcm3528 gets merged into mach-bcm (Stephen Warren)
 * at91 gains some common clock framework support (Alexandre Belloni,
   Jean-Jacques Hiblot and other French people).
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1.4.12 (GNU/Linux)
 
 iQIVAwUAUz/yOWCrR//JCVInAQLOPBAAwTMkMrD8S8ggz6vfiQHZNdRPAC7NUJ46
 +eYKmBVi5d6EdnjNuRElWENsh0ZosSAUFHrXsIC2NdH9sAJ9HOqWNNLymuA59Jo9
 HZ/Ze6xQXDPNV7TROPoXuIli/2OCOXyyQHJsfI7h9V3PCx31qo0B5OdCxU0mtXK6
 r1giREhnJFwfQMF/FTdnzhalFJoSjWwv/nkpNmQDJKRLKj9GzwQqItqw68gV6RzU
 Gnt6YK+9xC1B0cfWTFhAm6kbr9i7mvHoMG5tE3no2uuJMn4K7TgeMqOyvPWhmUeB
 EZi656szT1m5VfRWOqG+7coZO2VM4GO4NI0Xfin3GHllugOYls1il/FAfCPMLiwh
 RvuOmQGCkLIpdkuHop5QaI/h1EzlHA59nzTjmGf1+wWPsm0CIg08XOD9izQbRnN9
 EmRqn1/8POIi17xcWyeMp8LB0APsTI+IflZFaYprEY9VlLLA/Pd+7udULhs8Bq8y
 1l6fB6aPZKnDKCBy/PEIR+y+EHFEbwfrx6zm/pxVDX6P5DlQMFWL78pdBoJUa2h8
 3pm/bSzNU5OSz1nJMLJv2jBTtnM5BvFgQBUi2qJ9Lr+nUhJXKCJ80kE/nOlXoCIU
 J952p3OhkYTQQcjuUVQeTXvRUOGB7mKok0pDFZNE6c7faqxTCudMABQq/KbMFstU
 eE3cH5FyYj4=
 =GcBb
 -----END PGP SIGNATURE-----

Merge tag 'cleanup-3.15' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc

Pull ARM SoC cleanups from Arnd Bergmann:
 "These cleanup patches are mainly move stuff around and should all be
  harmless.  They are mainly split out so that other branches can be
  based on top to avoid conflicts.

  Notable changes are:

   - We finally remove all mach/timex.h, after CLOCK_TICK_RATE is no
     longer used (Uwe Kleine-König)
   - The Qualcomm MSM platform is split out into legacy mach-msm and
     new-style mach-qcom, to allow easier maintainance of the new
     hardware support without regressions (Kumar Gala)
   - A rework of some of the Kconfig logic to simplify multiplatform
     support (Rob Herring)
   - Samsung Exynos gets closer to supporting multiplatform (Sachin
     Kamat and others)
   - mach-bcm3528 gets merged into mach-bcm (Stephen Warren)
   - at91 gains some common clock framework support (Alexandre Belloni,
     Jean-Jacques Hiblot and other French people)"

* tag 'cleanup-3.15' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (89 commits)
  ARM: hisi: select HAVE_ARM_SCU only for SMP
  ARM: efm32: allow uncompress debug output
  ARM: prima2: build reset code standalone
  ARM: at91: add PWM clock
  ARM: at91: move sam9261 SoC to common clk
  ARM: at91: prepare common clk transition for sam9261 SoC
  ARM: at91: updated the at91_dt_defconfig with support for the ADS7846
  ARM: at91: dt: sam9261: Device Tree support for the at91sam9261ek
  ARM: at91: dt: defconfig: Added the sam9261 to the list of DT-enabled SOCs
  ARM: at91: dt: Add at91sam9261 dt SoC support
  ARM: at91: switch sam9rl to common clock framework
  ARM: at91/dt: define main clk frequency of at91sam9rlek
  ARM: at91/dt: define at91sam9rl clocks
  ARM: at91: prepare common clk transition for sam9rl SoCs
  ARM: at91: prepare sam9 dt boards transition to common clk
  ARM: at91: dt: sam9rl: Device Tree for the at91sam9rlek
  ARM: at91/defconfig: Add the sam9rl to the list of DT-enabled SOCs
  ARM: at91: Add at91sam9rl DT SoC support
  ARM: at91: prepare at91sam9rl DT transition
  ARM: at91/defconfig: refresh at91sam9260_9g20_defconfig
  ...
2014-04-05 13:51:19 -07:00

494 lines
13 KiB
C

/*
* linux/arch/arm/mach-mmp/irq.c
*
* Generic IRQ handling, GPIO IRQ demultiplexing, etc.
* Copyright (C) 2008 - 2012 Marvell Technology Group Ltd.
*
* Author: Bin Yang <bin.yang@marvell.com>
* Haojian Zhuang <haojian.zhuang@gmail.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/module.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <asm/exception.h>
#include <asm/hardirq.h>
#include "irqchip.h"
#define MAX_ICU_NR 16
#define PJ1_INT_SEL 0x10c
#define PJ4_INT_SEL 0x104
/* bit fields in PJ1_INT_SEL and PJ4_INT_SEL */
#define SEL_INT_PENDING (1 << 6)
#define SEL_INT_NUM_MASK 0x3f
struct icu_chip_data {
int nr_irqs;
unsigned int virq_base;
unsigned int cascade_irq;
void __iomem *reg_status;
void __iomem *reg_mask;
unsigned int conf_enable;
unsigned int conf_disable;
unsigned int conf_mask;
unsigned int clr_mfp_irq_base;
unsigned int clr_mfp_hwirq;
struct irq_domain *domain;
};
struct mmp_intc_conf {
unsigned int conf_enable;
unsigned int conf_disable;
unsigned int conf_mask;
};
static void __iomem *mmp_icu_base;
static struct icu_chip_data icu_data[MAX_ICU_NR];
static int max_icu_nr;
extern void mmp2_clear_pmic_int(void);
static void icu_mask_ack_irq(struct irq_data *d)
{
struct irq_domain *domain = d->domain;
struct icu_chip_data *data = (struct icu_chip_data *)domain->host_data;
int hwirq;
u32 r;
hwirq = d->irq - data->virq_base;
if (data == &icu_data[0]) {
r = readl_relaxed(mmp_icu_base + (hwirq << 2));
r &= ~data->conf_mask;
r |= data->conf_disable;
writel_relaxed(r, mmp_icu_base + (hwirq << 2));
} else {
#ifdef CONFIG_CPU_MMP2
if ((data->virq_base == data->clr_mfp_irq_base)
&& (hwirq == data->clr_mfp_hwirq))
mmp2_clear_pmic_int();
#endif
r = readl_relaxed(data->reg_mask) | (1 << hwirq);
writel_relaxed(r, data->reg_mask);
}
}
static void icu_mask_irq(struct irq_data *d)
{
struct irq_domain *domain = d->domain;
struct icu_chip_data *data = (struct icu_chip_data *)domain->host_data;
int hwirq;
u32 r;
hwirq = d->irq - data->virq_base;
if (data == &icu_data[0]) {
r = readl_relaxed(mmp_icu_base + (hwirq << 2));
r &= ~data->conf_mask;
r |= data->conf_disable;
writel_relaxed(r, mmp_icu_base + (hwirq << 2));
} else {
r = readl_relaxed(data->reg_mask) | (1 << hwirq);
writel_relaxed(r, data->reg_mask);
}
}
static void icu_unmask_irq(struct irq_data *d)
{
struct irq_domain *domain = d->domain;
struct icu_chip_data *data = (struct icu_chip_data *)domain->host_data;
int hwirq;
u32 r;
hwirq = d->irq - data->virq_base;
if (data == &icu_data[0]) {
r = readl_relaxed(mmp_icu_base + (hwirq << 2));
r &= ~data->conf_mask;
r |= data->conf_enable;
writel_relaxed(r, mmp_icu_base + (hwirq << 2));
} else {
r = readl_relaxed(data->reg_mask) & ~(1 << hwirq);
writel_relaxed(r, data->reg_mask);
}
}
struct irq_chip icu_irq_chip = {
.name = "icu_irq",
.irq_mask = icu_mask_irq,
.irq_mask_ack = icu_mask_ack_irq,
.irq_unmask = icu_unmask_irq,
};
static void icu_mux_irq_demux(unsigned int irq, struct irq_desc *desc)
{
struct irq_domain *domain;
struct icu_chip_data *data;
int i;
unsigned long mask, status, n;
for (i = 1; i < max_icu_nr; i++) {
if (irq == icu_data[i].cascade_irq) {
domain = icu_data[i].domain;
data = (struct icu_chip_data *)domain->host_data;
break;
}
}
if (i >= max_icu_nr) {
pr_err("Spurious irq %d in MMP INTC\n", irq);
return;
}
mask = readl_relaxed(data->reg_mask);
while (1) {
status = readl_relaxed(data->reg_status) & ~mask;
if (status == 0)
break;
for_each_set_bit(n, &status, BITS_PER_LONG) {
generic_handle_irq(icu_data[i].virq_base + n);
}
}
}
static int mmp_irq_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hw)
{
irq_set_chip_and_handler(irq, &icu_irq_chip, handle_level_irq);
set_irq_flags(irq, IRQF_VALID);
return 0;
}
static int mmp_irq_domain_xlate(struct irq_domain *d, struct device_node *node,
const u32 *intspec, unsigned int intsize,
unsigned long *out_hwirq,
unsigned int *out_type)
{
*out_hwirq = intspec[0];
return 0;
}
const struct irq_domain_ops mmp_irq_domain_ops = {
.map = mmp_irq_domain_map,
.xlate = mmp_irq_domain_xlate,
};
static struct mmp_intc_conf mmp_conf = {
.conf_enable = 0x51,
.conf_disable = 0x0,
.conf_mask = 0x7f,
};
static struct mmp_intc_conf mmp2_conf = {
.conf_enable = 0x20,
.conf_disable = 0x0,
.conf_mask = 0x7f,
};
static void __exception_irq_entry mmp_handle_irq(struct pt_regs *regs)
{
int irq, hwirq;
hwirq = readl_relaxed(mmp_icu_base + PJ1_INT_SEL);
if (!(hwirq & SEL_INT_PENDING))
return;
hwirq &= SEL_INT_NUM_MASK;
irq = irq_find_mapping(icu_data[0].domain, hwirq);
handle_IRQ(irq, regs);
}
static void __exception_irq_entry mmp2_handle_irq(struct pt_regs *regs)
{
int irq, hwirq;
hwirq = readl_relaxed(mmp_icu_base + PJ4_INT_SEL);
if (!(hwirq & SEL_INT_PENDING))
return;
hwirq &= SEL_INT_NUM_MASK;
irq = irq_find_mapping(icu_data[0].domain, hwirq);
handle_IRQ(irq, regs);
}
/* MMP (ARMv5) */
void __init icu_init_irq(void)
{
int irq;
max_icu_nr = 1;
mmp_icu_base = ioremap(0xd4282000, 0x1000);
icu_data[0].conf_enable = mmp_conf.conf_enable;
icu_data[0].conf_disable = mmp_conf.conf_disable;
icu_data[0].conf_mask = mmp_conf.conf_mask;
icu_data[0].nr_irqs = 64;
icu_data[0].virq_base = 0;
icu_data[0].domain = irq_domain_add_legacy(NULL, 64, 0, 0,
&irq_domain_simple_ops,
&icu_data[0]);
for (irq = 0; irq < 64; irq++) {
icu_mask_irq(irq_get_irq_data(irq));
irq_set_chip_and_handler(irq, &icu_irq_chip, handle_level_irq);
set_irq_flags(irq, IRQF_VALID);
}
irq_set_default_host(icu_data[0].domain);
set_handle_irq(mmp_handle_irq);
}
/* MMP2 (ARMv7) */
void __init mmp2_init_icu(void)
{
int irq, end;
max_icu_nr = 8;
mmp_icu_base = ioremap(0xd4282000, 0x1000);
icu_data[0].conf_enable = mmp2_conf.conf_enable;
icu_data[0].conf_disable = mmp2_conf.conf_disable;
icu_data[0].conf_mask = mmp2_conf.conf_mask;
icu_data[0].nr_irqs = 64;
icu_data[0].virq_base = 0;
icu_data[0].domain = irq_domain_add_legacy(NULL, 64, 0, 0,
&irq_domain_simple_ops,
&icu_data[0]);
icu_data[1].reg_status = mmp_icu_base + 0x150;
icu_data[1].reg_mask = mmp_icu_base + 0x168;
icu_data[1].clr_mfp_irq_base = icu_data[0].virq_base +
icu_data[0].nr_irqs;
icu_data[1].clr_mfp_hwirq = 1; /* offset to IRQ_MMP2_PMIC_BASE */
icu_data[1].nr_irqs = 2;
icu_data[1].cascade_irq = 4;
icu_data[1].virq_base = icu_data[0].virq_base + icu_data[0].nr_irqs;
icu_data[1].domain = irq_domain_add_legacy(NULL, icu_data[1].nr_irqs,
icu_data[1].virq_base, 0,
&irq_domain_simple_ops,
&icu_data[1]);
icu_data[2].reg_status = mmp_icu_base + 0x154;
icu_data[2].reg_mask = mmp_icu_base + 0x16c;
icu_data[2].nr_irqs = 2;
icu_data[2].cascade_irq = 5;
icu_data[2].virq_base = icu_data[1].virq_base + icu_data[1].nr_irqs;
icu_data[2].domain = irq_domain_add_legacy(NULL, icu_data[2].nr_irqs,
icu_data[2].virq_base, 0,
&irq_domain_simple_ops,
&icu_data[2]);
icu_data[3].reg_status = mmp_icu_base + 0x180;
icu_data[3].reg_mask = mmp_icu_base + 0x17c;
icu_data[3].nr_irqs = 3;
icu_data[3].cascade_irq = 9;
icu_data[3].virq_base = icu_data[2].virq_base + icu_data[2].nr_irqs;
icu_data[3].domain = irq_domain_add_legacy(NULL, icu_data[3].nr_irqs,
icu_data[3].virq_base, 0,
&irq_domain_simple_ops,
&icu_data[3]);
icu_data[4].reg_status = mmp_icu_base + 0x158;
icu_data[4].reg_mask = mmp_icu_base + 0x170;
icu_data[4].nr_irqs = 5;
icu_data[4].cascade_irq = 17;
icu_data[4].virq_base = icu_data[3].virq_base + icu_data[3].nr_irqs;
icu_data[4].domain = irq_domain_add_legacy(NULL, icu_data[4].nr_irqs,
icu_data[4].virq_base, 0,
&irq_domain_simple_ops,
&icu_data[4]);
icu_data[5].reg_status = mmp_icu_base + 0x15c;
icu_data[5].reg_mask = mmp_icu_base + 0x174;
icu_data[5].nr_irqs = 15;
icu_data[5].cascade_irq = 35;
icu_data[5].virq_base = icu_data[4].virq_base + icu_data[4].nr_irqs;
icu_data[5].domain = irq_domain_add_legacy(NULL, icu_data[5].nr_irqs,
icu_data[5].virq_base, 0,
&irq_domain_simple_ops,
&icu_data[5]);
icu_data[6].reg_status = mmp_icu_base + 0x160;
icu_data[6].reg_mask = mmp_icu_base + 0x178;
icu_data[6].nr_irqs = 2;
icu_data[6].cascade_irq = 51;
icu_data[6].virq_base = icu_data[5].virq_base + icu_data[5].nr_irqs;
icu_data[6].domain = irq_domain_add_legacy(NULL, icu_data[6].nr_irqs,
icu_data[6].virq_base, 0,
&irq_domain_simple_ops,
&icu_data[6]);
icu_data[7].reg_status = mmp_icu_base + 0x188;
icu_data[7].reg_mask = mmp_icu_base + 0x184;
icu_data[7].nr_irqs = 2;
icu_data[7].cascade_irq = 55;
icu_data[7].virq_base = icu_data[6].virq_base + icu_data[6].nr_irqs;
icu_data[7].domain = irq_domain_add_legacy(NULL, icu_data[7].nr_irqs,
icu_data[7].virq_base, 0,
&irq_domain_simple_ops,
&icu_data[7]);
end = icu_data[7].virq_base + icu_data[7].nr_irqs;
for (irq = 0; irq < end; irq++) {
icu_mask_irq(irq_get_irq_data(irq));
if (irq == icu_data[1].cascade_irq ||
irq == icu_data[2].cascade_irq ||
irq == icu_data[3].cascade_irq ||
irq == icu_data[4].cascade_irq ||
irq == icu_data[5].cascade_irq ||
irq == icu_data[6].cascade_irq ||
irq == icu_data[7].cascade_irq) {
irq_set_chip(irq, &icu_irq_chip);
irq_set_chained_handler(irq, icu_mux_irq_demux);
} else {
irq_set_chip_and_handler(irq, &icu_irq_chip,
handle_level_irq);
}
set_irq_flags(irq, IRQF_VALID);
}
irq_set_default_host(icu_data[0].domain);
set_handle_irq(mmp2_handle_irq);
}
#ifdef CONFIG_OF
static int __init mmp_init_bases(struct device_node *node)
{
int ret, nr_irqs, irq, i = 0;
ret = of_property_read_u32(node, "mrvl,intc-nr-irqs", &nr_irqs);
if (ret) {
pr_err("Not found mrvl,intc-nr-irqs property\n");
return ret;
}
mmp_icu_base = of_iomap(node, 0);
if (!mmp_icu_base) {
pr_err("Failed to get interrupt controller register\n");
return -ENOMEM;
}
icu_data[0].virq_base = 0;
icu_data[0].domain = irq_domain_add_linear(node, nr_irqs,
&mmp_irq_domain_ops,
&icu_data[0]);
for (irq = 0; irq < nr_irqs; irq++) {
ret = irq_create_mapping(icu_data[0].domain, irq);
if (!ret) {
pr_err("Failed to mapping hwirq\n");
goto err;
}
if (!irq)
icu_data[0].virq_base = ret;
}
icu_data[0].nr_irqs = nr_irqs;
return 0;
err:
if (icu_data[0].virq_base) {
for (i = 0; i < irq; i++)
irq_dispose_mapping(icu_data[0].virq_base + i);
}
irq_domain_remove(icu_data[0].domain);
iounmap(mmp_icu_base);
return -EINVAL;
}
static int __init mmp_of_init(struct device_node *node,
struct device_node *parent)
{
int ret;
ret = mmp_init_bases(node);
if (ret < 0)
return ret;
icu_data[0].conf_enable = mmp_conf.conf_enable;
icu_data[0].conf_disable = mmp_conf.conf_disable;
icu_data[0].conf_mask = mmp_conf.conf_mask;
irq_set_default_host(icu_data[0].domain);
set_handle_irq(mmp_handle_irq);
max_icu_nr = 1;
return 0;
}
IRQCHIP_DECLARE(mmp_intc, "mrvl,mmp-intc", mmp_of_init);
static int __init mmp2_of_init(struct device_node *node,
struct device_node *parent)
{
int ret;
ret = mmp_init_bases(node);
if (ret < 0)
return ret;
icu_data[0].conf_enable = mmp2_conf.conf_enable;
icu_data[0].conf_disable = mmp2_conf.conf_disable;
icu_data[0].conf_mask = mmp2_conf.conf_mask;
irq_set_default_host(icu_data[0].domain);
set_handle_irq(mmp2_handle_irq);
max_icu_nr = 1;
return 0;
}
IRQCHIP_DECLARE(mmp2_intc, "mrvl,mmp2-intc", mmp2_of_init);
static int __init mmp2_mux_of_init(struct device_node *node,
struct device_node *parent)
{
struct resource res;
int i, ret, irq, j = 0;
u32 nr_irqs, mfp_irq;
if (!parent)
return -ENODEV;
i = max_icu_nr;
ret = of_property_read_u32(node, "mrvl,intc-nr-irqs",
&nr_irqs);
if (ret) {
pr_err("Not found mrvl,intc-nr-irqs property\n");
return -EINVAL;
}
ret = of_address_to_resource(node, 0, &res);
if (ret < 0) {
pr_err("Not found reg property\n");
return -EINVAL;
}
icu_data[i].reg_status = mmp_icu_base + res.start;
ret = of_address_to_resource(node, 1, &res);
if (ret < 0) {
pr_err("Not found reg property\n");
return -EINVAL;
}
icu_data[i].reg_mask = mmp_icu_base + res.start;
icu_data[i].cascade_irq = irq_of_parse_and_map(node, 0);
if (!icu_data[i].cascade_irq)
return -EINVAL;
icu_data[i].virq_base = 0;
icu_data[i].domain = irq_domain_add_linear(node, nr_irqs,
&mmp_irq_domain_ops,
&icu_data[i]);
for (irq = 0; irq < nr_irqs; irq++) {
ret = irq_create_mapping(icu_data[i].domain, irq);
if (!ret) {
pr_err("Failed to mapping hwirq\n");
goto err;
}
if (!irq)
icu_data[i].virq_base = ret;
}
icu_data[i].nr_irqs = nr_irqs;
if (!of_property_read_u32(node, "mrvl,clr-mfp-irq",
&mfp_irq)) {
icu_data[i].clr_mfp_irq_base = icu_data[i].virq_base;
icu_data[i].clr_mfp_hwirq = mfp_irq;
}
irq_set_chained_handler(icu_data[i].cascade_irq,
icu_mux_irq_demux);
max_icu_nr++;
return 0;
err:
if (icu_data[i].virq_base) {
for (j = 0; j < irq; j++)
irq_dispose_mapping(icu_data[i].virq_base + j);
}
irq_domain_remove(icu_data[i].domain);
return -EINVAL;
}
IRQCHIP_DECLARE(mmp2_mux_intc, "mrvl,mmp2-mux-intc", mmp2_mux_of_init);
#endif