summaryrefslogtreecommitdiff
path: root/arch/arm
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm')
-rw-r--r--arch/arm/mach-tegra/Makefile1
-rw-r--r--arch/arm/mach-tegra/irq.c75
-rw-r--r--arch/arm/mach-tegra/pm-irq.c203
-rw-r--r--arch/arm/mach-tegra/pm-irq.h33
4 files changed, 312 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile
index e6fc2cdaf0fc..d801a9d47767 100644
--- a/arch/arm/mach-tegra/Makefile
+++ b/arch/arm/mach-tegra/Makefile
@@ -6,6 +6,7 @@ obj-y += clock.o
obj-y += timer.o
obj-y += pinmux.o
obj-y += powergate.o
+obj-$(CONFIG_PM_SLEEP) += pm-irq.o
obj-y += fuse.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += clock.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_clocks.o
diff --git a/arch/arm/mach-tegra/irq.c b/arch/arm/mach-tegra/irq.c
index 4956c3cea731..56c5c0771b5c 100644
--- a/arch/arm/mach-tegra/irq.c
+++ b/arch/arm/mach-tegra/irq.c
@@ -21,12 +21,14 @@
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
+#include <linux/syscore_ops.h>
#include <asm/hardware/gic.h>
#include <mach/iomap.h>
#include "board.h"
+#include "pm-irq.h"
#define INT_SYS_NR (INT_GPIO_BASE - INT_PRI_BASE)
#define INT_SYS_SZ (INT_SEC_BASE - INT_PRI_BASE)
@@ -57,6 +59,12 @@ static void __iomem *ictlr_reg_base[] = {
IO_ADDRESS(TEGRA_QUATERNARY_ICTLR_BASE),
};
+#ifdef CONFIG_PM
+static u32 cop_ier[NUM_ICTLRS];
+static u32 cpu_ier[NUM_ICTLRS];
+static u32 cpu_iep[NUM_ICTLRS];
+#endif
+
static inline void tegra_irq_write_mask(unsigned int irq, unsigned long reg)
{
void __iomem *base;
@@ -113,6 +121,70 @@ static int tegra_retrigger(struct irq_data *d)
return 1;
}
+static int tegra_set_type(struct irq_data *d, unsigned int flow_type)
+{
+ return tegra_pm_irq_set_wake_type(d->irq, flow_type);
+}
+
+
+#ifdef CONFIG_PM
+static int tegra_set_wake(struct irq_data *d, unsigned int enable)
+{
+ return tegra_pm_irq_set_wake(d->irq, enable);
+}
+
+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];
+ 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);
+ writel(~0, ictlr + ICTLR_COP_IER_CLR);
+ }
+ 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(0, 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,
+};
+
+static int tegra_legacy_irq_syscore_init(void)
+{
+ register_syscore_ops(&tegra_legacy_irq_syscore_ops);
+
+ return 0;
+}
+subsys_initcall(tegra_legacy_irq_syscore_init);
+#else
+#define tegra_set_wake NULL
+#endif
+
void __init tegra_init_irq(void)
{
int i;
@@ -128,6 +200,9 @@ void __init tegra_init_irq(void)
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;
gic_init(0, 29, IO_ADDRESS(TEGRA_ARM_INT_DIST_BASE),
IO_ADDRESS(TEGRA_ARM_PERIF_BASE + 0x100));
diff --git a/arch/arm/mach-tegra/pm-irq.c b/arch/arm/mach-tegra/pm-irq.c
new file mode 100644
index 000000000000..67a543675512
--- /dev/null
+++ b/arch/arm/mach-tegra/pm-irq.c
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2011 Google, Inc.
+ *
+ * Author:
+ * Colin Cross <ccross@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/moduleparam.h>
+#include <linux/seq_file.h>
+#include <linux/syscore_ops.h>
+
+#include <mach/iomap.h>
+
+#include "pm-irq.h"
+
+#define PMC_CTRL 0x0
+#define PMC_CTRL_LATCH_WAKEUPS (1 << 5)
+#define PMC_WAKE_MASK 0xc
+#define PMC_WAKE_LEVEL 0x10
+#define PMC_WAKE_STATUS 0x14
+#define PMC_SW_WAKE_STATUS 0x18
+
+static void __iomem *pmc = IO_ADDRESS(TEGRA_PMC_BASE);
+
+static u32 tegra_lp0_wake_enb;
+static u32 tegra_lp0_wake_level;
+static u32 tegra_lp0_wake_level_any;
+static int tegra_prevent_lp0;
+
+static bool debug_lp0;
+module_param(debug_lp0, bool, S_IRUGO | S_IWUSR);
+
+static bool warn_prevent_lp0;
+module_param(warn_prevent_lp0, bool, S_IRUGO | S_IWUSR);
+
+bool tegra_pm_irq_lp0_allowed(void)
+{
+ return (tegra_prevent_lp0 == 0);
+}
+
+/* ensures that sufficient time is passed for a register write to
+ * serialize into the 32KHz domain */
+static void pmc_32kwritel(u32 val, unsigned long offs)
+{
+ writel(val, pmc + offs);
+ udelay(130);
+}
+
+int tegra_pm_irq_set_wake(int irq, int enable)
+{
+ int wake = tegra_irq_to_wake(irq);
+
+ if (wake == -EALREADY) {
+ /* EALREADY means wakeup event already accounted for */
+ return 0;
+ } else if (wake == -ENOTSUPP) {
+ /* ENOTSUPP means LP0 not supported with this wake source */
+ WARN(enable && warn_prevent_lp0, "irq %d prevents lp0\n", irq);
+ if (enable)
+ tegra_prevent_lp0++;
+ else if (!WARN_ON(tegra_prevent_lp0 == 0))
+ tegra_prevent_lp0--;
+ return 0;
+ } else if (wake < 0) {
+ return -EINVAL;
+ }
+
+ if (enable)
+ tegra_lp0_wake_enb |= 1 << wake;
+ else
+ tegra_lp0_wake_enb &= ~(1 << wake);
+
+ return 0;
+}
+
+int tegra_pm_irq_set_wake_type(int irq, int flow_type)
+{
+ int wake = tegra_irq_to_wake(irq);
+
+ if (wake < 0)
+ return 0;
+
+ switch (flow_type) {
+ case IRQF_TRIGGER_FALLING:
+ case IRQF_TRIGGER_LOW:
+ tegra_lp0_wake_level &= ~(1 << wake);
+ tegra_lp0_wake_level_any &= ~(1 << wake);
+ break;
+ case IRQF_TRIGGER_HIGH:
+ case IRQF_TRIGGER_RISING:
+ tegra_lp0_wake_level |= 1 << wake;
+ tegra_lp0_wake_level_any &= ~(1 << wake);
+ break;
+
+ case IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING:
+ tegra_lp0_wake_level_any |= 1 << wake;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* translate lp0 wake sources back into irqs to catch edge triggered wakeups */
+static void tegra_pm_irq_syscore_resume(void)
+{
+ int wake;
+ int irq;
+ struct irq_desc *desc;
+ unsigned long wake_status = readl(pmc + PMC_WAKE_STATUS);
+
+ for_each_set_bit(wake, &wake_status, sizeof(wake_status) * 8) {
+ irq = tegra_wake_to_irq(wake);
+ if (!irq) {
+ pr_info("Resume caused by WAKE%d\n", wake);
+ continue;
+ }
+
+ desc = irq_to_desc(irq);
+ if (!desc || !desc->action || !desc->action->name) {
+ pr_info("Resume caused by WAKE%d, irq %d\n", wake, irq);
+ continue;
+ }
+
+ pr_info("Resume caused by WAKE%d, %s\n", wake,
+ desc->action->name);
+
+ generic_handle_irq(irq);
+ }
+}
+
+/* set up lp0 wake sources */
+static int tegra_pm_irq_syscore_suspend(void)
+{
+ u32 temp;
+ u32 status;
+ u32 lvl;
+ u32 wake_level;
+ u32 wake_enb;
+
+ pmc_32kwritel(0, PMC_SW_WAKE_STATUS);
+
+ temp = readl(pmc + PMC_CTRL);
+ temp |= PMC_CTRL_LATCH_WAKEUPS;
+ pmc_32kwritel(temp, PMC_CTRL);
+
+ temp &= ~PMC_CTRL_LATCH_WAKEUPS;
+ pmc_32kwritel(temp, PMC_CTRL);
+
+ status = readl(pmc + PMC_SW_WAKE_STATUS);
+
+ lvl = readl(pmc + PMC_WAKE_LEVEL);
+
+ /* flip the wakeup trigger for any-edge triggered pads
+ * which are currently asserting as wakeups */
+ lvl ^= status;
+ lvl &= tegra_lp0_wake_level_any;
+
+ wake_level = lvl | tegra_lp0_wake_level;
+ wake_enb = tegra_lp0_wake_enb;
+
+ if (debug_lp0) {
+ wake_level = lvl ^ status;
+ wake_enb = 0xffffffff;
+ }
+
+ writel(wake_level, pmc + PMC_WAKE_LEVEL);
+
+ writel(wake_enb, pmc + PMC_WAKE_MASK);
+
+ return 0;
+}
+
+static struct syscore_ops tegra_pm_irq_syscore_ops = {
+ .suspend = tegra_pm_irq_syscore_suspend,
+ .resume = tegra_pm_irq_syscore_resume,
+};
+
+static int tegra_pm_irq_syscore_init(void)
+{
+ register_syscore_ops(&tegra_pm_irq_syscore_ops);
+
+ return 0;
+}
+subsys_initcall(tegra_pm_irq_syscore_init);
diff --git a/arch/arm/mach-tegra/pm-irq.h b/arch/arm/mach-tegra/pm-irq.h
new file mode 100644
index 000000000000..8242b9202f80
--- /dev/null
+++ b/arch/arm/mach-tegra/pm-irq.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2011 Google, Inc.
+ *
+ * Author:
+ * Colin Cross <ccross@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that 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.
+ *
+ */
+
+#ifndef _MACH_TERA_PM_IRQ_H_
+#define _MACH_TERA_PM_IRQ_H_
+
+#ifdef CONFIG_PM
+int tegra_pm_irq_set_wake(int irq, int enable);
+int tegra_pm_irq_set_wake_type(int irq, int flow_type);
+bool tegra_pm_irq_lp0_allowed(void);
+int tegra_irq_to_wake(int irq);
+int tegra_wake_to_irq(int wake);
+#else
+static inline int tegra_pm_irq_set_wake_type(int irq, int flow_type)
+{
+ return 0;
+}
+#endif
+#endif