162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * Cpufreq driver for the loongson-2 processors
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * The 2E revision of loongson processor not support this feature.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2006 - 2008 Lemote Inc. & Institute of Computing Technology
762306a36Sopenharmony_ci * Author: Yanhua, yanh@lemote.com
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
1062306a36Sopenharmony_ci * License.  See the file "COPYING" in the main directory of this archive
1162306a36Sopenharmony_ci * for more details.
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/cpufreq.h>
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci#include <linux/err.h>
1962306a36Sopenharmony_ci#include <linux/delay.h>
2062306a36Sopenharmony_ci#include <linux/platform_device.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include <asm/idle.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include <asm/mach-loongson2ef/loongson.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic uint nowait;
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic void (*saved_cpu_wait) (void);
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic int loongson2_cpu_freq_notifier(struct notifier_block *nb,
3162306a36Sopenharmony_ci					unsigned long val, void *data);
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic struct notifier_block loongson2_cpufreq_notifier_block = {
3462306a36Sopenharmony_ci	.notifier_call = loongson2_cpu_freq_notifier
3562306a36Sopenharmony_ci};
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic int loongson2_cpu_freq_notifier(struct notifier_block *nb,
3862306a36Sopenharmony_ci					unsigned long val, void *data)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	if (val == CPUFREQ_POSTCHANGE)
4162306a36Sopenharmony_ci		current_cpu_data.udelay_val = loops_per_jiffy;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	return 0;
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci/*
4762306a36Sopenharmony_ci * Here we notify other drivers of the proposed change and the final change.
4862306a36Sopenharmony_ci */
4962306a36Sopenharmony_cistatic int loongson2_cpufreq_target(struct cpufreq_policy *policy,
5062306a36Sopenharmony_ci				     unsigned int index)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	unsigned int freq;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	freq =
5562306a36Sopenharmony_ci	    ((cpu_clock_freq / 1000) *
5662306a36Sopenharmony_ci	     loongson2_clockmod_table[index].driver_data) / 8;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	/* setting the cpu frequency */
5962306a36Sopenharmony_ci	loongson2_cpu_set_rate(freq);
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	return 0;
6262306a36Sopenharmony_ci}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic int loongson2_cpufreq_cpu_init(struct cpufreq_policy *policy)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	int i;
6762306a36Sopenharmony_ci	unsigned long rate;
6862306a36Sopenharmony_ci	int ret;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	rate = cpu_clock_freq / 1000;
7162306a36Sopenharmony_ci	if (!rate)
7262306a36Sopenharmony_ci		return -EINVAL;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	/* clock table init */
7562306a36Sopenharmony_ci	for (i = 2;
7662306a36Sopenharmony_ci	     (loongson2_clockmod_table[i].frequency != CPUFREQ_TABLE_END);
7762306a36Sopenharmony_ci	     i++)
7862306a36Sopenharmony_ci		loongson2_clockmod_table[i].frequency = (rate * i) / 8;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	ret = loongson2_cpu_set_rate(rate);
8162306a36Sopenharmony_ci	if (ret)
8262306a36Sopenharmony_ci		return ret;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	cpufreq_generic_init(policy, &loongson2_clockmod_table[0], 0);
8562306a36Sopenharmony_ci	return 0;
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_cistatic int loongson2_cpufreq_exit(struct cpufreq_policy *policy)
8962306a36Sopenharmony_ci{
9062306a36Sopenharmony_ci	return 0;
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic struct cpufreq_driver loongson2_cpufreq_driver = {
9462306a36Sopenharmony_ci	.name = "loongson2",
9562306a36Sopenharmony_ci	.init = loongson2_cpufreq_cpu_init,
9662306a36Sopenharmony_ci	.verify = cpufreq_generic_frequency_table_verify,
9762306a36Sopenharmony_ci	.target_index = loongson2_cpufreq_target,
9862306a36Sopenharmony_ci	.get = cpufreq_generic_get,
9962306a36Sopenharmony_ci	.exit = loongson2_cpufreq_exit,
10062306a36Sopenharmony_ci	.attr = cpufreq_generic_attr,
10162306a36Sopenharmony_ci};
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic const struct platform_device_id platform_device_ids[] = {
10462306a36Sopenharmony_ci	{
10562306a36Sopenharmony_ci		.name = "loongson2_cpufreq",
10662306a36Sopenharmony_ci	},
10762306a36Sopenharmony_ci	{}
10862306a36Sopenharmony_ci};
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(platform, platform_device_ids);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic struct platform_driver platform_driver = {
11362306a36Sopenharmony_ci	.driver = {
11462306a36Sopenharmony_ci		.name = "loongson2_cpufreq",
11562306a36Sopenharmony_ci	},
11662306a36Sopenharmony_ci	.id_table = platform_device_ids,
11762306a36Sopenharmony_ci};
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci/*
12062306a36Sopenharmony_ci * This is the simple version of Loongson-2 wait, Maybe we need do this in
12162306a36Sopenharmony_ci * interrupt disabled context.
12262306a36Sopenharmony_ci */
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic DEFINE_SPINLOCK(loongson2_wait_lock);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic void loongson2_cpu_wait(void)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	unsigned long flags;
12962306a36Sopenharmony_ci	u32 cpu_freq;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	spin_lock_irqsave(&loongson2_wait_lock, flags);
13262306a36Sopenharmony_ci	cpu_freq = readl(LOONGSON_CHIPCFG);
13362306a36Sopenharmony_ci	/* Put CPU into wait mode */
13462306a36Sopenharmony_ci	writel(readl(LOONGSON_CHIPCFG) & ~0x7, LOONGSON_CHIPCFG);
13562306a36Sopenharmony_ci	/* Restore CPU state */
13662306a36Sopenharmony_ci	writel(cpu_freq, LOONGSON_CHIPCFG);
13762306a36Sopenharmony_ci	spin_unlock_irqrestore(&loongson2_wait_lock, flags);
13862306a36Sopenharmony_ci	local_irq_enable();
13962306a36Sopenharmony_ci}
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_cistatic int __init cpufreq_init(void)
14262306a36Sopenharmony_ci{
14362306a36Sopenharmony_ci	int ret;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	/* Register platform stuff */
14662306a36Sopenharmony_ci	ret = platform_driver_register(&platform_driver);
14762306a36Sopenharmony_ci	if (ret)
14862306a36Sopenharmony_ci		return ret;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	pr_info("Loongson-2F CPU frequency driver\n");
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	cpufreq_register_notifier(&loongson2_cpufreq_notifier_block,
15362306a36Sopenharmony_ci				  CPUFREQ_TRANSITION_NOTIFIER);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	ret = cpufreq_register_driver(&loongson2_cpufreq_driver);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	if (!ret && !nowait) {
15862306a36Sopenharmony_ci		saved_cpu_wait = cpu_wait;
15962306a36Sopenharmony_ci		cpu_wait = loongson2_cpu_wait;
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	return ret;
16362306a36Sopenharmony_ci}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_cistatic void __exit cpufreq_exit(void)
16662306a36Sopenharmony_ci{
16762306a36Sopenharmony_ci	if (!nowait && saved_cpu_wait)
16862306a36Sopenharmony_ci		cpu_wait = saved_cpu_wait;
16962306a36Sopenharmony_ci	cpufreq_unregister_driver(&loongson2_cpufreq_driver);
17062306a36Sopenharmony_ci	cpufreq_unregister_notifier(&loongson2_cpufreq_notifier_block,
17162306a36Sopenharmony_ci				    CPUFREQ_TRANSITION_NOTIFIER);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	platform_driver_unregister(&platform_driver);
17462306a36Sopenharmony_ci}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_cimodule_init(cpufreq_init);
17762306a36Sopenharmony_cimodule_exit(cpufreq_exit);
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cimodule_param(nowait, uint, 0644);
18062306a36Sopenharmony_ciMODULE_PARM_DESC(nowait, "Disable Loongson-2F specific wait");
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ciMODULE_AUTHOR("Yanhua <yanh@lemote.com>");
18362306a36Sopenharmony_ciMODULE_DESCRIPTION("cpufreq driver for Loongson2F");
18462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
185