blob: 7fde2967f9d8a2665aae062bbf33552607084305 [file] [log] [blame]
/*
* Copyright (C) 2011 Google, Inc.
*
* Author:
* Colin Cross <ccross@android.com>
*
* Copyright (c) 2010-2013, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/irqnr.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/irqchip/arm-gic.h>
#include <linux/syscore_ops.h>
#include <linux/clk/tegra.h>
#include <linux/irqchip.h>
#include <linux/irqchip/arm-gic.h>
#include <linux/irqchip/tegra.h>
#include <linux/cpumask.h> /* Required by asm/hardware/gic.h */
#include <linux/cpu_pm.h>
#include <linux/tegra-soc.h>
/* HACK: will be removed once cpuidle is moved to drivers */
#include "../../arch/arm/mach-tegra/pm.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
#ifdef CONFIG_ARCH_TEGRA_2x_SOC
#define NUM_ICTLRS 4
#else
#define NUM_ICTLRS 5
#endif
#define FIRST_LEGACY_IRQ 32
#define ARM_VERSION_CORTEX_A15 0xC0F
static u32 gic_version;
static void __iomem *gic_dist_base;
static void __iomem *gic_cpu_base;
static u32 gic_affinity[INT_GIC_NR/4];
static int num_ictlrs;
static void __iomem **ictlr_reg_base;
#ifdef CONFIG_PM_SLEEP
static u32 cop_ier[NUM_ICTLRS];
static u32 cop_iep[NUM_ICTLRS];
static u32 cpu_ier[NUM_ICTLRS];
static u32 cpu_iep[NUM_ICTLRS];
static u32 ictlr_wake_mask[NUM_ICTLRS];
#endif
#ifdef CONFIG_FIQ
static void tegra_legacy_select_fiq(unsigned int irq, bool fiq)
{
void __iomem *base;
pr_debug("%s: %d\n", __func__, irq);
irq -= FIRST_LEGACY_IRQ;
base = ictlr_reg_base[irq>>5];
writel(fiq << (irq & 31), base + ICTLR_CPU_IEP_CLASS);
}
static void tegra_fiq_mask(struct irq_data *d)
{
void __iomem *base;
int leg_irq;
if (d->irq < FIRST_LEGACY_IRQ)
return;
leg_irq = d->irq - FIRST_LEGACY_IRQ;
base = ictlr_reg_base[leg_irq >> 5];
writel(1 << (leg_irq & 31), base + ICTLR_CPU_IER_CLR);
}
static void tegra_fiq_unmask(struct irq_data *d)
{
void __iomem *base;
int leg_irq;
if (d->irq < FIRST_LEGACY_IRQ)
return;
leg_irq = d->irq - FIRST_LEGACY_IRQ;
base = ictlr_reg_base[leg_irq >> 5];
writel(1 << (leg_irq & 31), base + ICTLR_CPU_IER_SET);
}
void tegra_fiq_enable(int irq)
{
/* enable FIQ */
u32 val = readl(gic_cpu_base + GIC_CPU_CTRL);
val &= ~8; /* pass FIQs through */
val |= 2; /* enableNS */
writel(val, gic_cpu_base + GIC_CPU_CTRL);
tegra_legacy_select_fiq(irq, true);
tegra_fiq_unmask(irq_get_irq_data(irq));
}
void tegra_fiq_disable(int irq)
{
tegra_fiq_mask(irq_get_irq_data(irq));
tegra_legacy_select_fiq(irq, false);
}
#endif /* CONFIG_FIQ */
#if defined(CONFIG_HOTPLUG_CPU) || defined(CONFIG_PM_SLEEP)
void tegra_gic_cpu_disable(bool disable_pass_through)
{
u32 gic_cpu_ctrl = 0;
#ifndef CONFIG_ARCH_TEGRA_2x_SOC
if (disable_pass_through) {
if (gic_version == GIC_V2)
gic_cpu_ctrl = 0x1E0;
else
gic_cpu_ctrl = 2;
}
#endif
writel(gic_cpu_ctrl, gic_cpu_base + GIC_CPU_CTRL);
}
#endif
#ifdef CONFIG_PM_SLEEP
int tegra_gic_pending_interrupt(void)
{
u32 irq = readl(gic_cpu_base + GIC_CPU_HIGHPRI);
irq &= 0x3FF;
return irq;
}
#ifndef CONFIG_ARCH_TEGRA_2x_SOC
void tegra_gic_dist_disable(void)
{
writel(0, gic_dist_base + GIC_DIST_CTRL);
}
void tegra_gic_dist_enable(void)
{
writel(1, gic_dist_base + GIC_DIST_CTRL);
}
void tegra_gic_disable_affinity(void)
{
unsigned int i;
BUG_ON(is_lp_cluster());
/* The GIC distributor TARGET register is one byte per IRQ. */
for (i = 32; i < INT_GIC_NR; i += 4) {
/* Save the affinity. */
gic_affinity[i/4] = __raw_readl(gic_dist_base +
GIC_DIST_TARGET + i);
/* Force this interrupt to CPU0. */
__raw_writel(0x01010101, gic_dist_base + GIC_DIST_TARGET + i);
}
wmb();
}
void tegra_gic_restore_affinity(void)
{
unsigned int i;
BUG_ON(is_lp_cluster());
/* The GIC distributor TARGET register is one byte per IRQ. */
for (i = 32; i < INT_GIC_NR; i += 4) {
#ifdef CONFIG_BUG
u32 reg = __raw_readl(gic_dist_base + GIC_DIST_TARGET + i);
if (reg & 0xFEFEFEFE)
panic("GIC affinity changed!");
#endif
/* Restore this interrupt's affinity. */
__raw_writel(gic_affinity[i/4], gic_dist_base +
GIC_DIST_TARGET + i);
}
wmb();
}
void tegra_gic_affinity_to_cpu0(void)
{
unsigned int i;
BUG_ON(is_lp_cluster());
for (i = 32; i < INT_GIC_NR; i += 4)
__raw_writel(0x01010101, gic_dist_base + GIC_DIST_TARGET + i);
wmb();
}
#endif /* CONFIG_ARCH_TEGRA_2x_SOC */
static int tegra_gic_notifier(struct notifier_block *self,
unsigned long cmd, void *v)
{
switch (cmd) {
case CPU_PM_ENTER:
writel(0x1E0, gic_cpu_base + GIC_CPU_CTRL);
break;
}
return NOTIFY_OK;
}
static struct notifier_block tegra_gic_notifier_block = {
.notifier_call = tegra_gic_notifier,
};
#endif /* CONFIG_PM_SLEEP */
int tegra_update_lp1_irq_wake(unsigned int irq, bool enable)
{
#ifdef CONFIG_PM_SLEEP
u8 index;
u32 mask;
BUG_ON(irq < FIRST_LEGACY_IRQ ||
irq >= FIRST_LEGACY_IRQ + num_ictlrs * 32);
index = ((irq - FIRST_LEGACY_IRQ) / 32);
mask = BIT((irq - FIRST_LEGACY_IRQ) % 32);
if (enable)
ictlr_wake_mask[index] |= mask;
else
ictlr_wake_mask[index] &= ~mask;
#endif
return 0;
}
static inline void tegra_irq_write_mask(unsigned int irq, unsigned long reg)
{
void __iomem *base;
u32 mask;
BUG_ON(irq < FIRST_LEGACY_IRQ ||
irq >= FIRST_LEGACY_IRQ + num_ictlrs * 32);
base = ictlr_reg_base[(irq - FIRST_LEGACY_IRQ) / 32];
mask = BIT((irq - FIRST_LEGACY_IRQ) % 32);
__raw_writel(mask, base + reg);
}
static void tegra_mask(struct irq_data *d)
{
if (d->irq < FIRST_LEGACY_IRQ)
return;
tegra_irq_write_mask(d->irq, ICTLR_CPU_IER_CLR);
}
static void tegra_unmask(struct irq_data *d)
{
if (d->irq < FIRST_LEGACY_IRQ)
return;
tegra_irq_write_mask(d->irq, ICTLR_CPU_IER_SET);
}
static void tegra_ack(struct irq_data *d)
{
if (d->irq < FIRST_LEGACY_IRQ)
return;
tegra_irq_write_mask(d->irq, ICTLR_CPU_IEP_FIR_CLR);
}
static void tegra_eoi(struct irq_data *d)
{
if (d->irq < FIRST_LEGACY_IRQ)
return;
tegra_irq_write_mask(d->irq, ICTLR_CPU_IEP_FIR_CLR);
}
static int tegra_retrigger(struct irq_data *d)
{
if (d->irq < FIRST_LEGACY_IRQ)
return 0;
tegra_irq_write_mask(d->irq, ICTLR_CPU_IEP_FIR_SET);
return 1;
}
static int tegra_set_type(struct irq_data *d, unsigned int flow_type)
{
int wake_size;
int wake_list[PMC_MAX_WAKE_COUNT];
int i;
int err = 0;
int ret;
tegra_irq_to_wake(d->irq, wake_list, &wake_size);
for (i = 0; i < wake_size; i++) {
ret = tegra_pm_irq_set_wake_type(wake_list[i], flow_type);
if (ret < 0) {
pr_err("Set lp0 wake type=%d fail for irq=%d, wake%d ret=%d\n",
flow_type, d->irq, wake_list[i], ret);
if (!err)
err = ret;
}
}
return err;
}
#ifdef CONFIG_PM_SLEEP
/*
* Caller ensures that tegra_set_wake (irq_set_wake callback)
* is called for non-gpio wake sources only
*/
static int tegra_set_wake(struct irq_data *d, unsigned int enable)
{
int ret;
int wake_size;
int wake_list[PMC_MAX_WAKE_COUNT];
int i;
int err = 0;
tegra_irq_to_wake(d->irq, wake_list, &wake_size);
for (i = 0; i < wake_size; i++) {
/* pmc lp0 wake enable for non-gpio wake sources */
ret = tegra_pm_irq_set_wake(wake_list[i], enable);
if (ret < 0) {
pr_err("Failed lp0 wake %s for irq=%d, wake%d ret=%d\n",
(enable ? "enable" : "disable"), d->irq,
wake_list[i], ret);
if (!err)
err = ret;
}
}
/* lp1 wake enable for wake sources */
ret = tegra_update_lp1_irq_wake(d->irq, enable);
if (ret)
pr_err("Failed lp1 wake %s for irq=%d\n",
(enable ? "enable" : "disable"), d->irq);
return ret;
}
static int tegra_legacy_irq_suspend(void)
{
unsigned long flags;
int i;
local_irq_save(flags);
for (i = 0; i < NUM_ICTLRS; i++) {
void __iomem *ictlr = ictlr_reg_base[i];
/* save interrupt state */
cpu_ier[i] = readl(ictlr + ICTLR_CPU_IER);
cpu_iep[i] = readl(ictlr + ICTLR_CPU_IEP_CLASS);
cop_ier[i] = readl(ictlr + ICTLR_COP_IER);
cop_iep[i] = readl(ictlr + ICTLR_COP_IEP_CLASS);
/* disable COP interrupts */
writel(~0, ictlr + ICTLR_COP_IER_CLR);
/* disable CPU interrupts */
writel(~0, ictlr + ICTLR_CPU_IER_CLR);
/* enable lp1 wake sources */
writel(ictlr_wake_mask[i], ictlr + ICTLR_CPU_IER_SET);
}
local_irq_restore(flags);
return 0;
}
static void tegra_legacy_irq_resume(void)
{
unsigned long flags;
int i;
local_irq_save(flags);
for (i = 0; i < NUM_ICTLRS; i++) {
void __iomem *ictlr = ictlr_reg_base[i];
writel(cpu_iep[i], ictlr + ICTLR_CPU_IEP_CLASS);
writel(~0ul, ictlr + ICTLR_CPU_IER_CLR);
writel(cpu_ier[i], ictlr + ICTLR_CPU_IER_SET);
writel(cop_iep[i], ictlr + ICTLR_COP_IEP_CLASS);
writel(~0ul, ictlr + ICTLR_COP_IER_CLR);
writel(cop_ier[i], ictlr + ICTLR_COP_IER_SET);
}
local_irq_restore(flags);
}
static struct syscore_ops tegra_legacy_irq_syscore_ops = {
.suspend = tegra_legacy_irq_suspend,
.resume = tegra_legacy_irq_resume,
.save = tegra_legacy_irq_suspend,
.restore = tegra_legacy_irq_resume,
};
static int __init tegra_legacy_irq_syscore_init(void)
{
register_syscore_ops(&tegra_legacy_irq_syscore_ops);
return 0;
}
#else
#define tegra_set_wake NULL
#endif
void tegra_init_legacy_irq_cop(void)
{
int i;
for (i = 0; i < NUM_ICTLRS; i++) {
void __iomem *ictlr = ictlr_reg_base[i];
writel(~0, ictlr + ICTLR_COP_IER_CLR);
writel(0, ictlr + ICTLR_COP_IEP_CLASS);
}
}
static int __init tegra_gic_of_init(struct device_node *node,
struct device_node *parent)
{
int i;
struct device_node *arm_gic_np =
of_find_compatible_node(NULL, NULL, "arm,cortex-a15-gic");
struct device_node *tegra_gic_np =
of_find_compatible_node(NULL, NULL, "nvidia,tegra-gic");
tegra_wakeup_table_init();
gic_dist_base = of_iomap(arm_gic_np, 0);
gic_cpu_base = of_iomap(arm_gic_np, 1);
gic_version = (readl(gic_dist_base + 0xFE8) & 0xF0) >> 4;
/* Retrieve # of ictrls from DT and fallback to gic dist */
if (of_property_read_u32(tegra_gic_np, "num-ictrls", &num_ictlrs))
num_ictlrs = readl_relaxed(gic_dist_base + GIC_DIST_CTR) & 0x1f;
pr_info("the number of interrupt controllers found is %d", num_ictlrs);
ictlr_reg_base = kzalloc(sizeof(void *) * num_ictlrs, GFP_KERNEL);
tegra_clocks_init();
for (i = 0; i < num_ictlrs; i++) {
ictlr_reg_base[i] = of_iomap(node, i);
if (!ictlr_reg_base[i]) {
pr_info("failed to get the right register\n");
return -EINVAL;
}
writel(~0, ictlr_reg_base[i] + ICTLR_CPU_IER_CLR);
writel(0, ictlr_reg_base[i] + ICTLR_CPU_IEP_CLASS);
writel(~0, ictlr_reg_base[i] + ICTLR_CPU_IEP_FIR_CLR);
}
gic_arch_extn.irq_ack = tegra_ack;
gic_arch_extn.irq_eoi = tegra_eoi;
gic_arch_extn.irq_mask = tegra_mask;
gic_arch_extn.irq_unmask = tegra_unmask;
gic_arch_extn.irq_retrigger = tegra_retrigger;
gic_arch_extn.irq_set_type = tegra_set_type;
gic_arch_extn.irq_set_wake = tegra_set_wake;
gic_arch_extn.flags = IRQCHIP_MASK_ON_SUSPEND;
#ifdef CONFIG_PM_SLEEP
tegra_legacy_irq_syscore_init();
if (gic_version == GIC_V2)
cpu_pm_register_notifier(&tegra_gic_notifier_block);
#endif
return 0;
}
IRQCHIP_DECLARE(tegra_gic, "nvidia,tegra-gic", tegra_gic_of_init);