blob: 570db3c1a2172d8c7354521a6ef57a63a96c57ee [file] [log] [blame]
/*
* Generic IRQ handling, APB Peripheral IRQ demultiplexing, etc.
*
* Author: Jisheng Zhang <jszhang@marvell.com>
* Copyright: Marvell International Ltd.
*
* 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/init.h>
#include <linux/export.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/io.h>
#include <linux/cpu_pm.h>
#include <asm/hardware/gic.h>
#include <asm/mach/irq.h>
#include <asm/hardware/cache-l2x0.h>
#define MAX_ICTL_NR 2
#define APB_ICTL_INTEN 0x00
#define APB_ICTL_INTMASK 0x08
#define APB_ICTL_FINALSTATUS 0x30
struct apb_ictl_data {
int nr_irqs;
unsigned int virq_base;
unsigned int cascade_irq;
void __iomem *ictl_base;
struct irq_domain *domain;
#ifdef CONFIG_CPU_PM
u32 ictl_intmask;
u32 ictl_inten;
#endif
};
static struct apb_ictl_data apb_data[MAX_ICTL_NR];
static int max_ictl_nr;
static inline u32 rdl(struct apb_ictl_data *ictl, int offset)
{
return readl_relaxed(ictl->ictl_base + offset);
}
static inline void wrl(struct apb_ictl_data *ictl, int offset, u32 value)
{
writel_relaxed(value, ictl->ictl_base + offset);
}
static void apb_enable_irq(struct irq_data *d)
{
u32 u;
struct irq_domain *domain = d->domain;
struct apb_ictl_data *data = (struct apb_ictl_data *)domain->host_data;
int pin = d->irq - data->virq_base;
u = rdl(data, APB_ICTL_INTMASK);
u &= ~(1 << (pin & 31));
wrl(data, APB_ICTL_INTMASK, u);
u = rdl(data, APB_ICTL_INTEN);
u |= (1 << (pin & 31));
wrl(data, APB_ICTL_INTEN, u);
}
static void apb_disable_irq(struct irq_data *d)
{
u32 u;
struct irq_domain *domain = d->domain;
struct apb_ictl_data *data = (struct apb_ictl_data *)domain->host_data;
int pin = d->irq - data->virq_base;
u = rdl(data, APB_ICTL_INTMASK);
u |= (1 << (pin & 31));
wrl(data, APB_ICTL_INTMASK, u);
u = rdl(data, APB_ICTL_INTEN);
u &= ~(1 << (pin & 31));
wrl(data, APB_ICTL_INTEN, u);
}
static void apb_mask_irq(struct irq_data *d)
{
u32 u;
struct irq_domain *domain = d->domain;
struct apb_ictl_data *data = (struct apb_ictl_data *)domain->host_data;
int pin = d->irq - data->virq_base;
u = rdl(data, APB_ICTL_INTMASK);
u |= (1 << (pin & 31));
wrl(data, APB_ICTL_INTMASK, u);
}
static void apb_unmask_irq(struct irq_data *d)
{
u32 u;
struct irq_domain *domain = d->domain;
struct apb_ictl_data *data = (struct apb_ictl_data *)domain->host_data;
int pin = d->irq - data->virq_base;
u = rdl(data, APB_ICTL_INTMASK);
u &= ~(1 << (pin & 31));
wrl(data, APB_ICTL_INTMASK, u);
}
static struct irq_chip apb_irq_chip = {
.name = "apb_ictl",
.irq_enable = apb_enable_irq,
.irq_disable = apb_disable_irq,
.irq_mask = apb_mask_irq,
.irq_unmask = apb_unmask_irq,
};
static void apb_irq_demux(unsigned int irq, struct irq_desc *desc)
{
int i;
struct irq_domain *domain;
struct apb_ictl_data *data;
unsigned long mask, status, n;
struct irq_chip *chip = irq_desc_get_chip(desc);
chained_irq_enter(chip, desc);
for (i = 0; i < max_ictl_nr; i++) {
if (irq == apb_data[i].cascade_irq) {
domain = apb_data[i].domain;
data = (struct apb_ictl_data *)domain->host_data;
break;
}
}
if (i >= max_ictl_nr) {
chained_irq_exit(chip, desc);
pr_err("Spurious irq %d in APB ICTL\n", irq);
return;
}
mask = rdl(data, APB_ICTL_INTMASK);
while (1) {
status = rdl(data, APB_ICTL_FINALSTATUS);
status &= ~mask;
if (status == 0)
break;
for_each_set_bit(n, &status, BITS_PER_LONG) {
generic_handle_irq(apb_data[i].virq_base + n);
}
}
chained_irq_exit(chip, desc);
}
static int apb_irq_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hw)
{
irq_set_chip_and_handler(irq, &apb_irq_chip, handle_level_irq);
set_irq_flags(irq, IRQF_VALID);
return 0;
}
static int apb_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 apb_irq_domain_ops = {
.map = apb_irq_domain_map,
.xlate = apb_irq_domain_xlate,
};
#ifdef CONFIG_CPU_PM
static inline void apb_ictl_save(unsigned int ictl_nr)
{
struct apb_ictl_data *ictl = &apb_data[ictl_nr];
ictl->ictl_intmask = rdl(ictl, APB_ICTL_INTMASK);
ictl->ictl_inten = rdl(ictl, APB_ICTL_INTEN);
}
static inline void apb_ictl_restore(unsigned int ictl_nr)
{
struct apb_ictl_data *ictl = &apb_data[ictl_nr];
wrl(ictl, APB_ICTL_INTMASK, ictl->ictl_intmask);
wrl(ictl, APB_ICTL_INTEN, ictl->ictl_inten);
}
static int apb_ictl_notifier(struct notifier_block *self, unsigned long cmd, void *v)
{
int i;
for (i = 0; i < max_ictl_nr; i++) {
switch (cmd) {
case CPU_CLUSTER_PM_ENTER:
apb_ictl_save(i);
break;
case CPU_CLUSTER_PM_ENTER_FAILED:
case CPU_CLUSTER_PM_EXIT:
apb_ictl_restore(i);
break;
}
}
return NOTIFY_OK;
}
static struct notifier_block apb_ictl_notifier_block = {
.notifier_call = apb_ictl_notifier,
};
static void __init apb_ictl_pm_init(struct apb_ictl_data *ictl)
{
if (ictl == &apb_data[0])
cpu_pm_register_notifier(&apb_ictl_notifier_block);
}
#else
static void __init apb_ictl_pm_init(struct apb_ictl_data *ictl)
{
}
#endif
static int __init apb_ictl_init(struct device_node *node, struct device_node *parent)
{
int i, irq_base, ret;
struct resource res;
u32 cnt;
if ((max_ictl_nr + 1) > MAX_ICTL_NR) {
pr_err("Spurious APB ICTL DT node\n");
ret = -EINVAL;
goto err;
}
i = max_ictl_nr;
ret = of_property_read_u32(node, "ictl-nr-irqs",
&cnt);
if (ret) {
pr_err("Not found ictl-nr-irqs property\n");
ret = -EINVAL;
goto err;
}
apb_data[i].ictl_base = of_iomap(node, 0);
ret = of_address_to_resource(node, 0, &res);
if (!apb_data[i].ictl_base) {
pr_err("Not found reg property\n");
ret = -EINVAL;
goto err;
}
apb_data[i].cascade_irq = irq_of_parse_and_map(node, 0);
if (!apb_data[i].cascade_irq) {
pr_err("Not found irq property\n");
ret = -EINVAL;
goto err;
}
irq_base = irq_alloc_descs(-1, 0, cnt, 0);
if (irq_base < 0) {
pr_err("Failed to allocate IRQ numbers for apb ictl\n");
ret = irq_base;
goto err;
}
irq_set_chained_handler(apb_data[i].cascade_irq,
apb_irq_demux);
apb_data[i].nr_irqs = cnt;
apb_data[i].virq_base = irq_base;
apb_data[i].domain = irq_domain_add_legacy(node, cnt,
irq_base, 0,
&apb_irq_domain_ops,
&apb_data[i]);
++max_ictl_nr;
apb_ictl_pm_init(&apb_data[i]);
return 0;
err:
of_node_put(node);
return ret;
}
static const struct of_device_id berlin_dt_irq_match[] = {
{ .compatible = "arm,cortex-a9-gic", .data = gic_of_init, },
{ .compatible = "arm,cortex-a15-gic", .data = gic_of_init, },
{ .compatible = "snps,dw-apb-ictl", .data = apb_ictl_init, },
{},
};
void __init berlin_init_irq(void)
{
#ifndef CONFIG_BERLIN2CDP
l2x0_of_init(0x70c00000, 0xfeffffff);
#endif
of_irq_init(berlin_dt_irq_match);
}