blob: a6610122ebad48994aa8bbf47b6e6a071bf74f56 [file] [log] [blame]
/*
* arch/arch/mach-tegra/timer-t3.c
*
* Copyright (c) 2011-2013, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <linux/init.h>
#include <linux/err.h>
#include <linux/sched.h>
#include <linux/time.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/clockchips.h>
#include <linux/clocksource.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/smp.h>
#include <linux/syscore_ops.h>
#include <linux/cpu.h>
#include <linux/export.h>
#include <linux/tegra-soc.h>
#include <linux/tegra-timer.h>
#include <linux/tegra-cpuidle.h>
#ifndef CONFIG_ARM64
#include <linux/sched_clock.h>
#endif
#include <linux/of.h>
#include <linux/of_irq.h>
#include <asm/mach/time.h>
#include <asm/smp_plat.h>
#include <mach/irqs.h>
#if defined(CONFIG_ARM_ARCH_TIMER) || defined(CONFIG_HAVE_ARM_TWD)
#define TEST_LP2_WAKE_TIMERS 0
/*
* Timers usage:
* TMR1 - used as general CPU timer.
* TMR2 - used by AVP.
* TMR3 - used by CPU0 for LP2 wakeup.
* TMR4 - used by CPU1 for LP2 wakeup.
* TMR5 - used by CPU2 for LP2 wakeup.
* TMR6 - used by CPU3 for LP2 wakeup.
* TMR7 - watchdog, for generic purpose.
* TMR8 - Free.
* TMR9 - Free.
* TMR10 - watchdog, suspend/resume recovery
*/
#if defined(CONFIG_PM_SLEEP)
static cpumask_t wake_timer_canceled;
static cpumask_t wake_timer_ready;
#endif
#ifdef CONFIG_PM_SLEEP
static u32 lp2_wake_timers[] = {
TIMER3_OFFSET,
#ifdef CONFIG_SMP
TIMER4_OFFSET,
TIMER5_OFFSET,
TIMER6_OFFSET,
#endif
};
static irqreturn_t tegra_lp2wake_interrupt(int irq, void *dev_id)
{
int cpu = (int)dev_id;
int base;
base = lp2_wake_timers[cpu];
timer_writel(1<<30, base + TIMER_PCR);
return IRQ_HANDLED;
}
#define LP2_TIMER_IRQ_ACTION(cpu) { \
.name = "tmr_lp2wake_cpu" __stringify(cpu), \
.flags = IRQF_DISABLED, \
.handler = tegra_lp2wake_interrupt, \
.dev_id = (void*)cpu, \
}
static struct irqaction tegra_lp2wake_irq[] = {
LP2_TIMER_IRQ_ACTION(0),
#ifdef CONFIG_SMP
LP2_TIMER_IRQ_ACTION(1),
LP2_TIMER_IRQ_ACTION(2),
LP2_TIMER_IRQ_ACTION(3),
#endif
};
/*
* To sanity test LP2 timer interrupts for CPU 0-3, enable this flag and check
* /proc/interrupts for timer interrupts. CPUs 0-3 should have one interrupt
* counted against them for tmr_lp2wake_cpu<n>, where <n> is the CPU number.
*/
#if TEST_LP2_WAKE_TIMERS
static void test_lp2_wake_timer(unsigned int cpu)
{
unsigned long cycles = 50000;
unsigned int base = lp2_wake_timers[cpu];
static bool tested[4] = {false, false, false, false};
/* Don't repeat the test process on hotplug restart. */
if (!tested[cpu]) {
timer_writel(0, base + TIMER_PTV);
if (cycles) {
u32 reg = 0x80000000ul | min(0x1ffffffful, cycles);
timer_writel(reg, base + TIMER_PTV);
tested[cpu] = true;
}
}
}
#else
static inline void test_lp2_wake_timer(unsigned int cpu) {}
#endif
static int tegra3_resume_wake_timer(unsigned int cpu)
{
#ifdef CONFIG_SMP
int ret = irq_set_affinity(tegra_lp2wake_irq[cpu].irq, cpumask_of(cpu));
if (ret) {
pr_err("Failed to set affinity for LP2 timer IRQ to "
"CPU %d: irq=%d, ret=%d\n", cpu,
tegra_lp2wake_irq[cpu].irq, ret);
return ret;
}
#endif
cpumask_set_cpu(cpu, &wake_timer_ready);
return 0;
}
static void tegra3_register_wake_timer(unsigned int cpu)
{
int ret;
ret = setup_irq(tegra_lp2wake_irq[cpu].irq, &tegra_lp2wake_irq[cpu]);
if (ret) {
pr_err("Failed to register LP2 timer IRQ for CPU %d: "
"irq=%d, ret=%d\n", cpu,
tegra_lp2wake_irq[cpu].irq, ret);
goto fail;
}
ret = tegra3_resume_wake_timer(cpu);
if (ret)
goto fail;
test_lp2_wake_timer(cpu);
return;
fail:
tegra_pd_in_idle(false);
}
#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_HOTPLUG_CPU)
static void tegra3_suspend_wake_timer(unsigned int cpu)
{
cpumask_clear_cpu(cpu, &wake_timer_ready);
#ifdef CONFIG_SMP
/* Reassign the affinity of the wake IRQ to any ready CPU. */
for_each_cpu_not(cpu, &wake_timer_ready)
{
(void)irq_set_affinity(tegra_lp2wake_irq[cpu].irq,
cpumask_of(cpumask_any(&wake_timer_ready)));
}
#endif
}
static void tegra3_unregister_wake_timer(unsigned int cpu)
{
tegra3_suspend_wake_timer(cpu);
/* Dispose of this IRQ. */
remove_irq(tegra_lp2wake_irq[cpu].irq, &tegra_lp2wake_irq[cpu]);
}
#endif
void tegra3_lp2_set_trigger(unsigned long cycles)
{
int cpu = cpu_logical_map(smp_processor_id());
int base;
base = lp2_wake_timers[cpu];
timer_writel(0, base + TIMER_PTV);
if (cycles) {
u32 reg = 0x80000000ul | min(0x1ffffffful, cycles);
timer_writel(reg, base + TIMER_PTV);
}
}
EXPORT_SYMBOL(tegra3_lp2_set_trigger);
unsigned long tegra3_lp2_timer_remain(void)
{
int cpu = cpu_logical_map(smp_processor_id());
if (cpumask_test_and_clear_cpu(cpu, &wake_timer_canceled))
return -ETIME;
return timer_readl(lp2_wake_timers[cpu] + TIMER_PCR) & 0x1ffffffful;
}
int tegra3_is_cpu_wake_timer_ready(unsigned int cpu)
{
return cpumask_test_cpu(cpu, &wake_timer_ready);
}
void tegra3_lp2_timer_cancel_secondary(void)
{
int cpu;
int base;
for (cpu = 1; cpu < ARRAY_SIZE(lp2_wake_timers); cpu++) {
base = lp2_wake_timers[cpu];
cpumask_set_cpu(cpu, &wake_timer_canceled);
timer_writel(0, base + TIMER_PTV);
timer_writel(1<<30, base + TIMER_PCR);
}
}
#endif
void __init tegra30_init_timer(void)
{
#ifdef CONFIG_PM_SLEEP
tegra3_register_wake_timer(0);
#endif
}
#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_HOTPLUG_CPU)
static int hotplug_notify(struct notifier_block *self,
unsigned long action, void *cpu)
{
switch (action) {
case CPU_ONLINE:
tegra3_register_wake_timer((unsigned int)cpu);
break;
case CPU_ONLINE_FROZEN:
tegra3_resume_wake_timer((unsigned int)cpu);
break;
case CPU_DOWN_PREPARE:
tegra3_unregister_wake_timer((unsigned int)cpu);
break;
case CPU_DOWN_PREPARE_FROZEN:
tegra3_suspend_wake_timer((unsigned int)cpu);
break;
default:
break;
}
return NOTIFY_OK;
}
static struct notifier_block __cpuinitdata hotplug_notifier_block = {
.notifier_call = hotplug_notify,
};
int __init hotplug_cpu_register(struct device_node *np)
{
int cpu;
for (cpu = 0;cpu < 4;cpu++) {
tegra_lp2wake_irq[cpu].irq =
irq_of_parse_and_map(np, cpu + 2);
if (tegra_lp2wake_irq[cpu].irq <= 0) {
pr_err("Failed to map wakeup timer IRQ\n");
BUG();
}
}
return register_cpu_notifier(&hotplug_notifier_block);
}
#endif
#endif