blob: 5ada4d09080f028f0146a2a548a06af587f86f0e [file] [log] [blame]
/*
* Copyright 2015 ambarella, Inc.
*
*
* 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/>.
*
* Maintainer: Jorney Tu <qtu@ambarella.com>
*/
#include <linux/cpuidle.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/time.h>
#include <linux/delay.h>
#include <linux/suspend.h>
#include <asm/cpuidle.h>
#include <asm/proc-fns.h>
#include <asm/smp_scu.h>
#include <linux/interrupt.h>
#include <linux/percpu.h>
#include <linux/cpu.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include "../../kernel/time/tick-internal.h"
#define DEFAULT_FREQENCY_INDEX 1
#define MIN(x,y) ( (x) < (y) ? (x) : (y) )
#define CPUIDLE_DBG 0
#if CPUIDLE_DBG
#define dbg_print(fmt, args...) printk(fmt, ##args)
#else
#define dbg_print(fmt, args...) do{}while(0)
#endif
struct ambarella_cpuidle_info {
unsigned int core_old_freqency ;
unsigned int cortex_old_freqency ;
unsigned int freq_tbl_cnt;
struct cpufreq_frequency_table *freq_tbl;
struct clk *core_clk ; // pll_out_core !!!
struct clk *cortex_clk ;
};
static struct ambarella_cpuidle_info *info = NULL;
static int ambarella_cpufreq_probe(void)
{
const struct property *prop;
struct cpufreq_frequency_table *freqs;
struct device_node *np;
int i, count;
int ret = 0;
const __be32 *value;
info = kmalloc(sizeof(struct ambarella_cpuidle_info), GFP_KERNEL);
if (!info) {
pr_err("kmalloc ambarella_cpuidle_info failed !\n");
return -ENOMEM;
}
np = of_find_node_by_path("/cpus");
if (!np) {
pr_err("No cpu node found");
ret = -EFAULT;
goto info_mem;
}
prop = of_find_property(np, "cpufreq_tbl", NULL);
if (!prop || !prop->value) {
pr_err("Invalid cpufreq_tbl");
ret = -ENODEV;
goto node_put;
}
count = prop->length / sizeof(u32);
value = prop->value;
freqs = kmalloc(sizeof(struct cpufreq_frequency_table) * count,GFP_KERNEL);
if (!freqs) {
ret = -ENOMEM;
goto node_put;
}
for (i = 0; i < count; i++) {
freqs[i].index = i;
freqs[i].frequency = be32_to_cpup(value++);
}
info->freq_tbl_cnt = count;
info->freq_tbl = freqs;
info->core_clk = clk_get(NULL, "pll_out_core");
if (IS_ERR(info->core_clk)){
pr_err("unable get core clk\n");
ret = -EFAULT;
goto freq_mem;
}
info->cortex_clk = clk_get(NULL, "gclk_cortex");
if (IS_ERR(info->cortex_clk)){
pr_err("unable get cortex clk\n");
ret = -EFAULT;
goto freq_mem;
}
of_node_put(np);
return ret;
freq_mem:
kfree(freqs);
node_put:
of_node_put(np);
info_mem:
kfree(info);
return ret;
}
static unsigned int ambarella_cur_freq(struct clk *clk)
{
return clk_get_rate(clk) / 1000;
}
static int ambarella_cpufreq_switch(unsigned int index)
{
int ret ;
unsigned int core_new_freqency = 0;
unsigned int cortex_new_freqency = 0;
if(index < 0 || 2 * index > info->freq_tbl_cnt)
index = 0;
info->core_old_freqency = ambarella_cur_freq(info->core_clk) / 2;
info->cortex_old_freqency = ambarella_cur_freq(info->cortex_clk);
core_new_freqency = info->freq_tbl[index * 2].frequency;
cortex_new_freqency = info->freq_tbl[index * 2 + 1].frequency;
ret = clk_set_rate(info->core_clk, core_new_freqency * 1000 * 2);
if (ret){
pr_err("Core clock set failed\n");
return -1;
}
ret = clk_set_rate(info->cortex_clk, cortex_new_freqency * 1000);
if (ret){
pr_err("Cortex clock set failed\n");
return -1;
}
dbg_print("Switch Core frequency from %d to %d\n", info->core_old_freqency, core_new_freqency);
dbg_print("Switch Cortex frequency from %d to %d\n", info->cortex_old_freqency, cortex_new_freqency);
return 0;
}
static int ambarella_cpufreq_recover(void)
{
int ret ;
unsigned int core_new_freqency = 0;
unsigned int cortex_new_freqency = 0;
core_new_freqency = info->core_old_freqency;
cortex_new_freqency = info->cortex_old_freqency;
info->core_old_freqency = ambarella_cur_freq(info->core_clk) / 2;
info->cortex_old_freqency = ambarella_cur_freq(info->cortex_clk);
ret = clk_set_rate(info->core_clk, core_new_freqency * 1000 * 2);
if (ret){
pr_err("Core clock set failed\n");
return -1;
}
ret = clk_set_rate(info->cortex_clk, cortex_new_freqency * 1000);
if (ret){
pr_err("Cortex clock set failed\n");
return -1;
}
dbg_print("Recover Core frequency from %d to %d\n", info->core_old_freqency, core_new_freqency);
dbg_print("Recover Cortex frequency from %d to %d\n", info->cortex_old_freqency, cortex_new_freqency);
return 0;
}
static int disable_percpu_timer(void)
{
struct clock_event_device *dev;
struct tick_device *td;
int cpu;
cpu = smp_processor_id();
td = &per_cpu(tick_cpu_device, cpu);
dev = td->evtdev;
clockevents_set_mode(dev, CLOCK_EVT_MODE_SHUTDOWN);
return 0;
}
static int enable_percpu_timer(void)
{
struct clock_event_device *dev;
struct tick_device *td;
int cpu;
cpu = smp_processor_id();
td = &per_cpu(tick_cpu_device, cpu);
dev = td->evtdev;
clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT);
return 0;
}
static void ambarella_idle_exec(unsigned int freq_index)
{
ambarella_cpufreq_switch(freq_index);
disable_percpu_timer();
cpu_do_idle();
enable_percpu_timer();
ambarella_cpufreq_recover();
}
static int ambarella_pwrdown_idle(struct cpuidle_device *dev,
struct cpuidle_driver *drv,
int index)
{
ambarella_idle_exec(DEFAULT_FREQENCY_INDEX);
return index;
}
static struct cpuidle_driver ambarella_idle_driver = {
.name = "ambarella_idle",
.states = {
ARM_CPUIDLE_WFI_STATE,
{
.name = "FG",
.desc = "FREQ Gate",
.flags = CPUIDLE_FLAG_TIMER_STOP,
.exit_latency = 30,
.power_usage = 50,
.target_residency = 400000, // for judging cpu idle time
.enter = ambarella_pwrdown_idle,
},
},
.state_count = 2,
};
static int __init ambarella_cpuidle_init(void)
{
if (ambarella_cpufreq_probe())
return -EFAULT;
return cpuidle_register(&ambarella_idle_driver, NULL);
}
module_init(ambarella_cpuidle_init);