diff options
Diffstat (limited to 'arch/arm')
-rw-r--r-- | arch/arm/mach-tegra/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/mach-tegra/irq.c | 75 | ||||
-rw-r--r-- | arch/arm/mach-tegra/pm-irq.c | 203 | ||||
-rw-r--r-- | arch/arm/mach-tegra/pm-irq.h | 33 |
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 |