aboutsummaryrefslogblamecommitdiffstats
path: root/arch/arm/mach-tegra/cpuidle.c
blob: 47d5996e596170bb08c48322750172ba45caad9c (plain) (tree)
1
2
3
4
5
6
7
8
9
10




                                 
                                               



                                           











                                                                              




















                               
 
                    





















































































































































































                                                                            
 

                                          





































                                                                             
                
 



                                                                            
         













                                                                       
 



                                                                         
 


























                                                           
/*
 * arch/arm/mach-tegra/cpuidle.c
 *
 * CPU idle driver for Tegra CPUs
 *
 * Copyright (c) 2010-2011, NVIDIA Corporation.
 * Copyright (c) 2011 Google, Inc.
 * Author: Colin Cross <ccross@android.com>
 *         Gary King <gking@nvidia.com>
 *
 * 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.
 */

#include <linux/kernel.h>
#include <linux/cpu.h>
#include <linux/cpuidle.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/sched.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/smp.h>
#include <linux/suspend.h>
#include <linux/tick.h>

#include <asm/cpu_pm.h>

#include <mach/iomap.h>
#include <mach/irqs.h>

#include <trace/events/power.h>

#include "cpuidle.h"
#include "pm.h"
#include "sleep.h"

int tegra_lp2_exit_latency;
static int tegra_lp2_power_off_time;
static unsigned int tegra_lp2_min_residency;

struct cpuidle_driver tegra_idle = {
	.name = "tegra_idle",
	.owner = THIS_MODULE,
};

static DEFINE_PER_CPU(struct cpuidle_device *, idle_devices);

static int tegra_idle_enter_lp3(struct cpuidle_device *dev,
	struct cpuidle_state *state)
{
	ktime_t enter, exit;
	s64 us;

	trace_power_start(POWER_CSTATE, 1, dev->cpu);

	local_irq_disable();
	local_fiq_disable();

	enter = ktime_get();

	tegra_cpu_wfi();

	exit = ktime_sub(ktime_get(), enter);
	us = ktime_to_us(exit);

	local_fiq_enable();
	local_irq_enable();
	return (int)us;
}

static bool lp2_in_idle __read_mostly = false;

#ifdef CONFIG_PM_SLEEP
static bool lp2_in_idle_modifiable __read_mostly = true;
static bool lp2_disabled_by_suspend;

void tegra_lp2_in_idle(bool enable)
{
	/* If LP2 in idle is permanently disabled it can't be re-enabled. */
	if (lp2_in_idle_modifiable) {
		lp2_in_idle = enable;
		lp2_in_idle_modifiable = enable;
		if (!enable)
			pr_warn("LP2 in idle disabled\n");
	}
}

void tegra_lp2_update_target_residency(struct cpuidle_state *state)
{
	state->target_residency = state->exit_latency +
		tegra_lp2_power_off_time;
	if (state->target_residency < tegra_lp2_min_residency)
		state->target_residency = tegra_lp2_min_residency;
}

static int tegra_idle_enter_lp2(struct cpuidle_device *dev,
	struct cpuidle_state *state)
{
	ktime_t enter, exit;
	s64 us;

	if (!lp2_in_idle || lp2_disabled_by_suspend ||
	    !tegra_lp2_is_allowed(dev, state)) {
		dev->last_state = &dev->states[0];
		return tegra_idle_enter_lp3(dev, state);
	}

	local_irq_disable();
	enter = ktime_get();

	tegra_cpu_idle_stats_lp2_ready(dev->cpu);
	tegra_idle_lp2(dev, state);

	exit = ktime_sub(ktime_get(), enter);
	us = ktime_to_us(exit);

	local_irq_enable();

	/* cpu clockevents may have been reset by powerdown */
	hrtimer_peek_ahead_timers();

	smp_rmb();

	/* Update LP2 latency provided no fall back to LP3 */
	if (state == dev->last_state) {
		tegra_lp2_set_global_latency(state);
		tegra_lp2_update_target_residency(state);
	}
	tegra_cpu_idle_stats_lp2_time(dev->cpu, us);

	return (int)us;
}
#endif

static int tegra_idle_prepare(struct cpuidle_device *dev)
{
#ifdef CONFIG_PM_SLEEP
	if (lp2_in_idle)
		dev->states[1].flags &= ~CPUIDLE_FLAG_IGNORE;
	else
		dev->states[1].flags |= CPUIDLE_FLAG_IGNORE;
#endif

	return 0;
}

static int tegra_cpuidle_register_device(unsigned int cpu)
{
	struct cpuidle_device *dev;
	struct cpuidle_state *state;

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev)
		return -ENOMEM;

	dev->state_count = 0;
	dev->cpu = cpu;

	state = &dev->states[0];
	snprintf(state->name, CPUIDLE_NAME_LEN, "LP3");
	snprintf(state->desc, CPUIDLE_DESC_LEN, "CPU flow-controlled");
	state->exit_latency = 10;
	state->target_residency = 10;
	state->power_usage = 600;
	state->flags = CPUIDLE_FLAG_TIME_VALID;
	state->enter = tegra_idle_enter_lp3;
	dev->safe_state = state;
	dev->state_count++;

#ifdef CONFIG_PM_SLEEP
	state = &dev->states[1];
	snprintf(state->name, CPUIDLE_NAME_LEN, "LP2");
	snprintf(state->desc, CPUIDLE_DESC_LEN, "CPU power-gate");
	state->exit_latency = tegra_cpu_power_good_time();

	state->target_residency = tegra_cpu_power_off_time() +
		tegra_cpu_power_good_time();
	if (state->target_residency < tegra_lp2_min_residency)
		state->target_residency = tegra_lp2_min_residency;
	state->power_usage = 0;
	state->flags = CPUIDLE_FLAG_TIME_VALID;
	state->enter = tegra_idle_enter_lp2;

	dev->power_specified = 1;
	dev->safe_state = state;
	dev->state_count++;
#endif

	dev->prepare = tegra_idle_prepare;

	if (cpuidle_register_device(dev)) {
		pr_err("CPU%u: failed to register idle device\n", cpu);
		kfree(dev);
		return -EIO;
	}
	per_cpu(idle_devices, cpu) = dev;
	return 0;
}

static int tegra_cpuidle_pm_notify(struct notifier_block *nb,
	unsigned long event, void *dummy)
{
#ifdef CONFIG_PM_SLEEP
	if (event == PM_SUSPEND_PREPARE)
		lp2_disabled_by_suspend = true;
	else if (event == PM_POST_SUSPEND)
		lp2_disabled_by_suspend = false;
#endif

	return NOTIFY_OK;
}

static struct notifier_block tegra_cpuidle_pm_notifier = {
	.notifier_call = tegra_cpuidle_pm_notify,
};

static int __init tegra_cpuidle_init(void)
{
	unsigned int cpu;
	int ret;

	ret = cpuidle_register_driver(&tegra_idle);
	if (ret)
		return ret;

#ifdef CONFIG_PM_SLEEP
	tegra_lp2_min_residency = tegra_cpu_lp2_min_residency();
	tegra_lp2_exit_latency = tegra_cpu_power_good_time();
	tegra_lp2_power_off_time = tegra_cpu_power_off_time();

	ret = tegra_cpudile_init_soc();
	if (ret)
		return ret;
#endif

	for_each_possible_cpu(cpu) {
		if (tegra_cpuidle_register_device(cpu))
			pr_err("CPU%u: error initializing idle loop\n", cpu);
	}

	register_pm_notifier(&tegra_cpuidle_pm_notifier);
	return 0;
}

static void __exit tegra_cpuidle_exit(void)
{
	unregister_pm_notifier(&tegra_cpuidle_pm_notifier);
	cpuidle_unregister_driver(&tegra_idle);
}

module_init(tegra_cpuidle_init);
module_exit(tegra_cpuidle_exit);

static int lp2_in_idle_set(const char *arg, const struct kernel_param *kp)
{
#ifdef CONFIG_PM_SLEEP
	int ret;

	/* If LP2 in idle is permanently disabled it can't be re-enabled. */
	if (lp2_in_idle_modifiable) {
		ret = param_set_bool(arg, kp);
		return ret;
	}
#endif
	return -ENODEV;
}

static int lp2_in_idle_get(char *buffer, const struct kernel_param *kp)
{
	return param_get_bool(buffer, kp);
}

static struct kernel_param_ops lp2_in_idle_ops = {
	.set = lp2_in_idle_set,
	.get = lp2_in_idle_get,
};
module_param_cb(lp2_in_idle, &lp2_in_idle_ops, &lp2_in_idle, 0644);

#if defined(CONFIG_DEBUG_FS) && defined(CONFIG_PM_SLEEP)
static int tegra_lp2_debug_open(struct inode *inode, struct file *file)
{
	return single_open(file, tegra_lp2_debug_show, inode->i_private);
}

static const struct file_operations tegra_lp2_debug_ops = {
	.open		= tegra_lp2_debug_open,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= single_release,
};

static int __init tegra_cpuidle_debug_init(void)
{
	struct dentry *dir;
	struct dentry *d;

	dir = debugfs_create_dir("cpuidle", NULL);
	if (!dir)
		return -ENOMEM;

	d = debugfs_create_file("lp2", S_IRUGO, dir, NULL,
		&tegra_lp2_debug_ops);
	if (!d)
		return -ENOMEM;

	return 0;
}

late_initcall(tegra_cpuidle_debug_init);
#endif