Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/davej/cpufreq
Pull cpufreq updates for 3.4 from Dave Jones: new drivers and some fixes. * 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/davej/cpufreq: provide disable_cpufreq() function to disable the API. EXYNOS5250: Add support cpufreq for EXYNOS5250 EXYNOS4X12: Add support cpufreq for EXYNOS4X12 [CPUFREQ] CPUfreq ondemand: update sampling rate without waiting for next sampling [CPUFREQ] Add S3C2416/S3C2450 cpufreq driver [CPUFREQ] Fix exposure of ARM_EXYNOS4210_CPUFREQ [CPUFREQ] EXYNOS4210: update the name of EXYNOS clock register [CPUFREQ] EXYNOS: Initialize locking_frequency with initial frequency [CPUFREQ] s3c64xx: Fix mis-cherry pick of VDDINT Fix up trivial conflicts in Kconfig and Makefile due to just changes next to each other (OMAP2PLUS changes vs some new EXYNOS cpufreq drivers).hifive-unleashed-5.1
commit
cf821923ba
|
@ -32,3 +32,5 @@ struct exynos_dvfs_info {
|
|||
};
|
||||
|
||||
extern int exynos4210_cpufreq_init(struct exynos_dvfs_info *);
|
||||
extern int exynos4x12_cpufreq_init(struct exynos_dvfs_info *);
|
||||
extern int exynos5250_cpufreq_init(struct exynos_dvfs_info *);
|
||||
|
|
|
@ -7,6 +7,28 @@ config ARM_OMAP2PLUS_CPUFREQ
|
|||
default ARCH_OMAP2PLUS
|
||||
select CPU_FREQ_TABLE
|
||||
|
||||
config ARM_S3C2416_CPUFREQ
|
||||
bool "S3C2416 CPU Frequency scaling support"
|
||||
depends on CPU_S3C2416
|
||||
help
|
||||
This adds the CPUFreq driver for the Samsung S3C2416 and
|
||||
S3C2450 SoC. The S3C2416 supports changing the rate of the
|
||||
armdiv clock source and also entering a so called dynamic
|
||||
voltage scaling mode in which it is possible to reduce the
|
||||
core voltage of the cpu.
|
||||
|
||||
If in doubt, say N.
|
||||
|
||||
config ARM_S3C2416_CPUFREQ_VCORESCALE
|
||||
bool "Allow voltage scaling for S3C2416 arm core (EXPERIMENTAL)"
|
||||
depends on ARM_S3C2416_CPUFREQ && REGULATOR && EXPERIMENTAL
|
||||
help
|
||||
Enable CPU voltage scaling when entering the dvs mode.
|
||||
It uses information gathered through existing hardware and
|
||||
tests but not documented in any datasheet.
|
||||
|
||||
If in doubt, say N.
|
||||
|
||||
config ARM_S3C64XX_CPUFREQ
|
||||
bool "Samsung S3C64XX"
|
||||
depends on CPU_S3C6410
|
||||
|
@ -30,6 +52,8 @@ config ARM_EXYNOS_CPUFREQ
|
|||
bool "SAMSUNG EXYNOS SoCs"
|
||||
depends on ARCH_EXYNOS
|
||||
select ARM_EXYNOS4210_CPUFREQ if CPU_EXYNOS4210
|
||||
select ARM_EXYNOS4X12_CPUFREQ if (SOC_EXYNOS4212 || SOC_EXYNOS4412)
|
||||
select ARM_EXYNOS5250_CPUFREQ if SOC_EXYNOS5250
|
||||
default y
|
||||
help
|
||||
This adds the CPUFreq driver common part for Samsung
|
||||
|
@ -39,6 +63,19 @@ config ARM_EXYNOS_CPUFREQ
|
|||
|
||||
config ARM_EXYNOS4210_CPUFREQ
|
||||
bool "Samsung EXYNOS4210"
|
||||
depends on ARCH_EXYNOS
|
||||
help
|
||||
This adds the CPUFreq driver for Samsung EXYNOS4210
|
||||
SoC (S5PV310 or S5PC210).
|
||||
|
||||
config ARM_EXYNOS4X12_CPUFREQ
|
||||
bool "Samsung EXYNOS4X12"
|
||||
help
|
||||
This adds the CPUFreq driver for Samsung EXYNOS4X12
|
||||
SoC (EXYNOS4212 or EXYNOS4412).
|
||||
|
||||
config ARM_EXYNOS5250_CPUFREQ
|
||||
bool "Samsung EXYNOS5250"
|
||||
help
|
||||
This adds the CPUFreq driver for Samsung EXYNOS5250
|
||||
SoC.
|
||||
|
|
|
@ -40,10 +40,13 @@ obj-$(CONFIG_X86_CPUFREQ_NFORCE2) += cpufreq-nforce2.o
|
|||
##################################################################################
|
||||
# ARM SoC drivers
|
||||
obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o
|
||||
obj-$(CONFIG_ARM_S3C2416_CPUFREQ) += s3c2416-cpufreq.o
|
||||
obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o
|
||||
obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o
|
||||
obj-$(CONFIG_ARM_EXYNOS_CPUFREQ) += exynos-cpufreq.o
|
||||
obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o
|
||||
obj-$(CONFIG_ARM_EXYNOS4X12_CPUFREQ) += exynos4x12-cpufreq.o
|
||||
obj-$(CONFIG_ARM_EXYNOS5250_CPUFREQ) += exynos5250-cpufreq.o
|
||||
obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o
|
||||
|
||||
##################################################################################
|
||||
|
|
|
@ -126,6 +126,15 @@ static int __init init_cpufreq_transition_notifier_list(void)
|
|||
}
|
||||
pure_initcall(init_cpufreq_transition_notifier_list);
|
||||
|
||||
static int off __read_mostly;
|
||||
int cpufreq_disabled(void)
|
||||
{
|
||||
return off;
|
||||
}
|
||||
void disable_cpufreq(void)
|
||||
{
|
||||
off = 1;
|
||||
}
|
||||
static LIST_HEAD(cpufreq_governor_list);
|
||||
static DEFINE_MUTEX(cpufreq_governor_mutex);
|
||||
|
||||
|
@ -1441,6 +1450,9 @@ int __cpufreq_driver_target(struct cpufreq_policy *policy,
|
|||
{
|
||||
int retval = -EINVAL;
|
||||
|
||||
if (cpufreq_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
pr_debug("target for CPU %u: %u kHz, relation %u\n", policy->cpu,
|
||||
target_freq, relation);
|
||||
if (cpu_online(policy->cpu) && cpufreq_driver->target)
|
||||
|
@ -1549,6 +1561,9 @@ int cpufreq_register_governor(struct cpufreq_governor *governor)
|
|||
if (!governor)
|
||||
return -EINVAL;
|
||||
|
||||
if (cpufreq_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
mutex_lock(&cpufreq_governor_mutex);
|
||||
|
||||
err = -EBUSY;
|
||||
|
@ -1572,6 +1587,9 @@ void cpufreq_unregister_governor(struct cpufreq_governor *governor)
|
|||
if (!governor)
|
||||
return;
|
||||
|
||||
if (cpufreq_disabled())
|
||||
return;
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
for_each_present_cpu(cpu) {
|
||||
if (cpu_online(cpu))
|
||||
|
@ -1814,6 +1832,9 @@ int cpufreq_register_driver(struct cpufreq_driver *driver_data)
|
|||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
if (cpufreq_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
if (!driver_data || !driver_data->verify || !driver_data->init ||
|
||||
((!driver_data->setpolicy) && (!driver_data->target)))
|
||||
return -EINVAL;
|
||||
|
@ -1901,6 +1922,9 @@ static int __init cpufreq_core_init(void)
|
|||
{
|
||||
int cpu;
|
||||
|
||||
if (cpufreq_disabled())
|
||||
return -ENODEV;
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
per_cpu(cpufreq_policy_cpu, cpu) = -1;
|
||||
init_rwsem(&per_cpu(cpu_policy_rwsem, cpu));
|
||||
|
|
|
@ -257,6 +257,62 @@ show_one(sampling_down_factor, sampling_down_factor);
|
|||
show_one(ignore_nice_load, ignore_nice);
|
||||
show_one(powersave_bias, powersave_bias);
|
||||
|
||||
/**
|
||||
* update_sampling_rate - update sampling rate effective immediately if needed.
|
||||
* @new_rate: new sampling rate
|
||||
*
|
||||
* If new rate is smaller than the old, simply updaing
|
||||
* dbs_tuners_int.sampling_rate might not be appropriate. For example,
|
||||
* if the original sampling_rate was 1 second and the requested new sampling
|
||||
* rate is 10 ms because the user needs immediate reaction from ondemand
|
||||
* governor, but not sure if higher frequency will be required or not,
|
||||
* then, the governor may change the sampling rate too late; up to 1 second
|
||||
* later. Thus, if we are reducing the sampling rate, we need to make the
|
||||
* new value effective immediately.
|
||||
*/
|
||||
static void update_sampling_rate(unsigned int new_rate)
|
||||
{
|
||||
int cpu;
|
||||
|
||||
dbs_tuners_ins.sampling_rate = new_rate
|
||||
= max(new_rate, min_sampling_rate);
|
||||
|
||||
for_each_online_cpu(cpu) {
|
||||
struct cpufreq_policy *policy;
|
||||
struct cpu_dbs_info_s *dbs_info;
|
||||
unsigned long next_sampling, appointed_at;
|
||||
|
||||
policy = cpufreq_cpu_get(cpu);
|
||||
if (!policy)
|
||||
continue;
|
||||
dbs_info = &per_cpu(od_cpu_dbs_info, policy->cpu);
|
||||
cpufreq_cpu_put(policy);
|
||||
|
||||
mutex_lock(&dbs_info->timer_mutex);
|
||||
|
||||
if (!delayed_work_pending(&dbs_info->work)) {
|
||||
mutex_unlock(&dbs_info->timer_mutex);
|
||||
continue;
|
||||
}
|
||||
|
||||
next_sampling = jiffies + usecs_to_jiffies(new_rate);
|
||||
appointed_at = dbs_info->work.timer.expires;
|
||||
|
||||
|
||||
if (time_before(next_sampling, appointed_at)) {
|
||||
|
||||
mutex_unlock(&dbs_info->timer_mutex);
|
||||
cancel_delayed_work_sync(&dbs_info->work);
|
||||
mutex_lock(&dbs_info->timer_mutex);
|
||||
|
||||
schedule_delayed_work_on(dbs_info->cpu, &dbs_info->work,
|
||||
usecs_to_jiffies(new_rate));
|
||||
|
||||
}
|
||||
mutex_unlock(&dbs_info->timer_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t store_sampling_rate(struct kobject *a, struct attribute *b,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
|
@ -265,7 +321,7 @@ static ssize_t store_sampling_rate(struct kobject *a, struct attribute *b,
|
|||
ret = sscanf(buf, "%u", &input);
|
||||
if (ret != 1)
|
||||
return -EINVAL;
|
||||
dbs_tuners_ins.sampling_rate = max(input, min_sampling_rate);
|
||||
update_sampling_rate(input);
|
||||
return count;
|
||||
}
|
||||
|
||||
|
|
|
@ -210,6 +210,8 @@ static int exynos_cpufreq_cpu_init(struct cpufreq_policy *policy)
|
|||
|
||||
cpufreq_frequency_table_get_attr(exynos_info->freq_table, policy->cpu);
|
||||
|
||||
locking_frequency = exynos_getspeed(0);
|
||||
|
||||
/* set the transition latency value */
|
||||
policy->cpuinfo.transition_latency = 100000;
|
||||
|
||||
|
@ -252,6 +254,10 @@ static int __init exynos_cpufreq_init(void)
|
|||
|
||||
if (soc_is_exynos4210())
|
||||
ret = exynos4210_cpufreq_init(exynos_info);
|
||||
else if (soc_is_exynos4212() || soc_is_exynos4412())
|
||||
ret = exynos4x12_cpufreq_init(exynos_info);
|
||||
else if (soc_is_exynos5250())
|
||||
ret = exynos5250_cpufreq_init(exynos_info);
|
||||
else
|
||||
pr_err("%s: CPU type not found\n", __func__);
|
||||
|
||||
|
|
|
@ -121,25 +121,25 @@ static void exynos4210_set_clkdiv(unsigned int div_index)
|
|||
|
||||
tmp = exynos4210_clkdiv_table[div_index].clkdiv;
|
||||
|
||||
__raw_writel(tmp, S5P_CLKDIV_CPU);
|
||||
__raw_writel(tmp, EXYNOS4_CLKDIV_CPU);
|
||||
|
||||
do {
|
||||
tmp = __raw_readl(S5P_CLKDIV_STATCPU);
|
||||
tmp = __raw_readl(EXYNOS4_CLKDIV_STATCPU);
|
||||
} while (tmp & 0x1111111);
|
||||
|
||||
/* Change Divider - CPU1 */
|
||||
|
||||
tmp = __raw_readl(S5P_CLKDIV_CPU1);
|
||||
tmp = __raw_readl(EXYNOS4_CLKDIV_CPU1);
|
||||
|
||||
tmp &= ~((0x7 << 4) | 0x7);
|
||||
|
||||
tmp |= ((clkdiv_cpu1[div_index][0] << 4) |
|
||||
(clkdiv_cpu1[div_index][1] << 0));
|
||||
|
||||
__raw_writel(tmp, S5P_CLKDIV_CPU1);
|
||||
__raw_writel(tmp, EXYNOS4_CLKDIV_CPU1);
|
||||
|
||||
do {
|
||||
tmp = __raw_readl(S5P_CLKDIV_STATCPU1);
|
||||
tmp = __raw_readl(EXYNOS4_CLKDIV_STATCPU1);
|
||||
} while (tmp & 0x11);
|
||||
}
|
||||
|
||||
|
@ -151,32 +151,32 @@ static void exynos4210_set_apll(unsigned int index)
|
|||
clk_set_parent(moutcore, mout_mpll);
|
||||
|
||||
do {
|
||||
tmp = (__raw_readl(S5P_CLKMUX_STATCPU)
|
||||
>> S5P_CLKSRC_CPU_MUXCORE_SHIFT);
|
||||
tmp = (__raw_readl(EXYNOS4_CLKMUX_STATCPU)
|
||||
>> EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT);
|
||||
tmp &= 0x7;
|
||||
} while (tmp != 0x2);
|
||||
|
||||
/* 2. Set APLL Lock time */
|
||||
__raw_writel(S5P_APLL_LOCKTIME, S5P_APLL_LOCK);
|
||||
__raw_writel(EXYNOS4_APLL_LOCKTIME, EXYNOS4_APLL_LOCK);
|
||||
|
||||
/* 3. Change PLL PMS values */
|
||||
tmp = __raw_readl(S5P_APLL_CON0);
|
||||
tmp = __raw_readl(EXYNOS4_APLL_CON0);
|
||||
tmp &= ~((0x3ff << 16) | (0x3f << 8) | (0x7 << 0));
|
||||
tmp |= exynos4210_apll_pms_table[index];
|
||||
__raw_writel(tmp, S5P_APLL_CON0);
|
||||
__raw_writel(tmp, EXYNOS4_APLL_CON0);
|
||||
|
||||
/* 4. wait_lock_time */
|
||||
do {
|
||||
tmp = __raw_readl(S5P_APLL_CON0);
|
||||
} while (!(tmp & (0x1 << S5P_APLLCON0_LOCKED_SHIFT)));
|
||||
tmp = __raw_readl(EXYNOS4_APLL_CON0);
|
||||
} while (!(tmp & (0x1 << EXYNOS4_APLLCON0_LOCKED_SHIFT)));
|
||||
|
||||
/* 5. MUX_CORE_SEL = APLL */
|
||||
clk_set_parent(moutcore, mout_apll);
|
||||
|
||||
do {
|
||||
tmp = __raw_readl(S5P_CLKMUX_STATCPU);
|
||||
tmp &= S5P_CLKMUX_STATCPU_MUXCORE_MASK;
|
||||
} while (tmp != (0x1 << S5P_CLKSRC_CPU_MUXCORE_SHIFT));
|
||||
tmp = __raw_readl(EXYNOS4_CLKMUX_STATCPU);
|
||||
tmp &= EXYNOS4_CLKMUX_STATCPU_MUXCORE_MASK;
|
||||
} while (tmp != (0x1 << EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT));
|
||||
}
|
||||
|
||||
bool exynos4210_pms_change(unsigned int old_index, unsigned int new_index)
|
||||
|
@ -198,10 +198,10 @@ static void exynos4210_set_frequency(unsigned int old_index,
|
|||
exynos4210_set_clkdiv(new_index);
|
||||
|
||||
/* 2. Change just s value in apll m,p,s value */
|
||||
tmp = __raw_readl(S5P_APLL_CON0);
|
||||
tmp = __raw_readl(EXYNOS4_APLL_CON0);
|
||||
tmp &= ~(0x7 << 0);
|
||||
tmp |= (exynos4210_apll_pms_table[new_index] & 0x7);
|
||||
__raw_writel(tmp, S5P_APLL_CON0);
|
||||
__raw_writel(tmp, EXYNOS4_APLL_CON0);
|
||||
} else {
|
||||
/* Clock Configuration Procedure */
|
||||
/* 1. Change the system clock divider values */
|
||||
|
@ -212,10 +212,10 @@ static void exynos4210_set_frequency(unsigned int old_index,
|
|||
} else if (old_index < new_index) {
|
||||
if (!exynos4210_pms_change(old_index, new_index)) {
|
||||
/* 1. Change just s value in apll m,p,s value */
|
||||
tmp = __raw_readl(S5P_APLL_CON0);
|
||||
tmp = __raw_readl(EXYNOS4_APLL_CON0);
|
||||
tmp &= ~(0x7 << 0);
|
||||
tmp |= (exynos4210_apll_pms_table[new_index] & 0x7);
|
||||
__raw_writel(tmp, S5P_APLL_CON0);
|
||||
__raw_writel(tmp, EXYNOS4_APLL_CON0);
|
||||
|
||||
/* 2. Change the system clock divider values */
|
||||
exynos4210_set_clkdiv(new_index);
|
||||
|
@ -253,24 +253,24 @@ int exynos4210_cpufreq_init(struct exynos_dvfs_info *info)
|
|||
if (IS_ERR(mout_apll))
|
||||
goto err_mout_apll;
|
||||
|
||||
tmp = __raw_readl(S5P_CLKDIV_CPU);
|
||||
tmp = __raw_readl(EXYNOS4_CLKDIV_CPU);
|
||||
|
||||
for (i = L0; i < CPUFREQ_LEVEL_END; i++) {
|
||||
tmp &= ~(S5P_CLKDIV_CPU0_CORE_MASK |
|
||||
S5P_CLKDIV_CPU0_COREM0_MASK |
|
||||
S5P_CLKDIV_CPU0_COREM1_MASK |
|
||||
S5P_CLKDIV_CPU0_PERIPH_MASK |
|
||||
S5P_CLKDIV_CPU0_ATB_MASK |
|
||||
S5P_CLKDIV_CPU0_PCLKDBG_MASK |
|
||||
S5P_CLKDIV_CPU0_APLL_MASK);
|
||||
tmp &= ~(EXYNOS4_CLKDIV_CPU0_CORE_MASK |
|
||||
EXYNOS4_CLKDIV_CPU0_COREM0_MASK |
|
||||
EXYNOS4_CLKDIV_CPU0_COREM1_MASK |
|
||||
EXYNOS4_CLKDIV_CPU0_PERIPH_MASK |
|
||||
EXYNOS4_CLKDIV_CPU0_ATB_MASK |
|
||||
EXYNOS4_CLKDIV_CPU0_PCLKDBG_MASK |
|
||||
EXYNOS4_CLKDIV_CPU0_APLL_MASK);
|
||||
|
||||
tmp |= ((clkdiv_cpu0[i][0] << S5P_CLKDIV_CPU0_CORE_SHIFT) |
|
||||
(clkdiv_cpu0[i][1] << S5P_CLKDIV_CPU0_COREM0_SHIFT) |
|
||||
(clkdiv_cpu0[i][2] << S5P_CLKDIV_CPU0_COREM1_SHIFT) |
|
||||
(clkdiv_cpu0[i][3] << S5P_CLKDIV_CPU0_PERIPH_SHIFT) |
|
||||
(clkdiv_cpu0[i][4] << S5P_CLKDIV_CPU0_ATB_SHIFT) |
|
||||
(clkdiv_cpu0[i][5] << S5P_CLKDIV_CPU0_PCLKDBG_SHIFT) |
|
||||
(clkdiv_cpu0[i][6] << S5P_CLKDIV_CPU0_APLL_SHIFT));
|
||||
tmp |= ((clkdiv_cpu0[i][0] << EXYNOS4_CLKDIV_CPU0_CORE_SHIFT) |
|
||||
(clkdiv_cpu0[i][1] << EXYNOS4_CLKDIV_CPU0_COREM0_SHIFT) |
|
||||
(clkdiv_cpu0[i][2] << EXYNOS4_CLKDIV_CPU0_COREM1_SHIFT) |
|
||||
(clkdiv_cpu0[i][3] << EXYNOS4_CLKDIV_CPU0_PERIPH_SHIFT) |
|
||||
(clkdiv_cpu0[i][4] << EXYNOS4_CLKDIV_CPU0_ATB_SHIFT) |
|
||||
(clkdiv_cpu0[i][5] << EXYNOS4_CLKDIV_CPU0_PCLKDBG_SHIFT) |
|
||||
(clkdiv_cpu0[i][6] << EXYNOS4_CLKDIV_CPU0_APLL_SHIFT));
|
||||
|
||||
exynos4210_clkdiv_table[i].clkdiv = tmp;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,536 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2012 Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* EXYNOS4X12 - CPU frequency scaling support
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/cpufreq.h>
|
||||
|
||||
#include <mach/regs-clock.h>
|
||||
#include <mach/cpufreq.h>
|
||||
|
||||
#define CPUFREQ_LEVEL_END (L13 + 1)
|
||||
|
||||
static int max_support_idx;
|
||||
static int min_support_idx = (CPUFREQ_LEVEL_END - 1);
|
||||
|
||||
static struct clk *cpu_clk;
|
||||
static struct clk *moutcore;
|
||||
static struct clk *mout_mpll;
|
||||
static struct clk *mout_apll;
|
||||
|
||||
struct cpufreq_clkdiv {
|
||||
unsigned int index;
|
||||
unsigned int clkdiv;
|
||||
unsigned int clkdiv1;
|
||||
};
|
||||
|
||||
static unsigned int exynos4x12_volt_table[CPUFREQ_LEVEL_END];
|
||||
|
||||
static struct cpufreq_frequency_table exynos4x12_freq_table[] = {
|
||||
{L0, 1500 * 1000},
|
||||
{L1, 1400 * 1000},
|
||||
{L2, 1300 * 1000},
|
||||
{L3, 1200 * 1000},
|
||||
{L4, 1100 * 1000},
|
||||
{L5, 1000 * 1000},
|
||||
{L6, 900 * 1000},
|
||||
{L7, 800 * 1000},
|
||||
{L8, 700 * 1000},
|
||||
{L9, 600 * 1000},
|
||||
{L10, 500 * 1000},
|
||||
{L11, 400 * 1000},
|
||||
{L12, 300 * 1000},
|
||||
{L13, 200 * 1000},
|
||||
{0, CPUFREQ_TABLE_END},
|
||||
};
|
||||
|
||||
static struct cpufreq_clkdiv exynos4x12_clkdiv_table[CPUFREQ_LEVEL_END];
|
||||
|
||||
static unsigned int clkdiv_cpu0_4212[CPUFREQ_LEVEL_END][8] = {
|
||||
/*
|
||||
* Clock divider value for following
|
||||
* { DIVCORE, DIVCOREM0, DIVCOREM1, DIVPERIPH,
|
||||
* DIVATB, DIVPCLK_DBG, DIVAPLL, DIVCORE2 }
|
||||
*/
|
||||
/* ARM L0: 1500 MHz */
|
||||
{ 0, 3, 7, 0, 6, 1, 2, 0 },
|
||||
|
||||
/* ARM L1: 1400 MHz */
|
||||
{ 0, 3, 7, 0, 6, 1, 2, 0 },
|
||||
|
||||
/* ARM L2: 1300 MHz */
|
||||
{ 0, 3, 7, 0, 5, 1, 2, 0 },
|
||||
|
||||
/* ARM L3: 1200 MHz */
|
||||
{ 0, 3, 7, 0, 5, 1, 2, 0 },
|
||||
|
||||
/* ARM L4: 1100 MHz */
|
||||
{ 0, 3, 6, 0, 4, 1, 2, 0 },
|
||||
|
||||
/* ARM L5: 1000 MHz */
|
||||
{ 0, 2, 5, 0, 4, 1, 1, 0 },
|
||||
|
||||
/* ARM L6: 900 MHz */
|
||||
{ 0, 2, 5, 0, 3, 1, 1, 0 },
|
||||
|
||||
/* ARM L7: 800 MHz */
|
||||
{ 0, 2, 5, 0, 3, 1, 1, 0 },
|
||||
|
||||
/* ARM L8: 700 MHz */
|
||||
{ 0, 2, 4, 0, 3, 1, 1, 0 },
|
||||
|
||||
/* ARM L9: 600 MHz */
|
||||
{ 0, 2, 4, 0, 3, 1, 1, 0 },
|
||||
|
||||
/* ARM L10: 500 MHz */
|
||||
{ 0, 2, 4, 0, 3, 1, 1, 0 },
|
||||
|
||||
/* ARM L11: 400 MHz */
|
||||
{ 0, 2, 4, 0, 3, 1, 1, 0 },
|
||||
|
||||
/* ARM L12: 300 MHz */
|
||||
{ 0, 2, 4, 0, 2, 1, 1, 0 },
|
||||
|
||||
/* ARM L13: 200 MHz */
|
||||
{ 0, 1, 3, 0, 1, 1, 1, 0 },
|
||||
};
|
||||
|
||||
static unsigned int clkdiv_cpu0_4412[CPUFREQ_LEVEL_END][8] = {
|
||||
/*
|
||||
* Clock divider value for following
|
||||
* { DIVCORE, DIVCOREM0, DIVCOREM1, DIVPERIPH,
|
||||
* DIVATB, DIVPCLK_DBG, DIVAPLL, DIVCORE2 }
|
||||
*/
|
||||
/* ARM L0: 1500 MHz */
|
||||
{ 0, 3, 7, 0, 6, 1, 2, 0 },
|
||||
|
||||
/* ARM L1: 1400 MHz */
|
||||
{ 0, 3, 7, 0, 6, 1, 2, 0 },
|
||||
|
||||
/* ARM L2: 1300 MHz */
|
||||
{ 0, 3, 7, 0, 5, 1, 2, 0 },
|
||||
|
||||
/* ARM L3: 1200 MHz */
|
||||
{ 0, 3, 7, 0, 5, 1, 2, 0 },
|
||||
|
||||
/* ARM L4: 1100 MHz */
|
||||
{ 0, 3, 6, 0, 4, 1, 2, 0 },
|
||||
|
||||
/* ARM L5: 1000 MHz */
|
||||
{ 0, 2, 5, 0, 4, 1, 1, 0 },
|
||||
|
||||
/* ARM L6: 900 MHz */
|
||||
{ 0, 2, 5, 0, 3, 1, 1, 0 },
|
||||
|
||||
/* ARM L7: 800 MHz */
|
||||
{ 0, 2, 5, 0, 3, 1, 1, 0 },
|
||||
|
||||
/* ARM L8: 700 MHz */
|
||||
{ 0, 2, 4, 0, 3, 1, 1, 0 },
|
||||
|
||||
/* ARM L9: 600 MHz */
|
||||
{ 0, 2, 4, 0, 3, 1, 1, 0 },
|
||||
|
||||
/* ARM L10: 500 MHz */
|
||||
{ 0, 2, 4, 0, 3, 1, 1, 0 },
|
||||
|
||||
/* ARM L11: 400 MHz */
|
||||
{ 0, 2, 4, 0, 3, 1, 1, 0 },
|
||||
|
||||
/* ARM L12: 300 MHz */
|
||||
{ 0, 2, 4, 0, 2, 1, 1, 0 },
|
||||
|
||||
/* ARM L13: 200 MHz */
|
||||
{ 0, 1, 3, 0, 1, 1, 1, 0 },
|
||||
};
|
||||
|
||||
static unsigned int clkdiv_cpu1_4212[CPUFREQ_LEVEL_END][2] = {
|
||||
/* Clock divider value for following
|
||||
* { DIVCOPY, DIVHPM }
|
||||
*/
|
||||
/* ARM L0: 1500 MHz */
|
||||
{ 6, 0 },
|
||||
|
||||
/* ARM L1: 1400 MHz */
|
||||
{ 6, 0 },
|
||||
|
||||
/* ARM L2: 1300 MHz */
|
||||
{ 5, 0 },
|
||||
|
||||
/* ARM L3: 1200 MHz */
|
||||
{ 5, 0 },
|
||||
|
||||
/* ARM L4: 1100 MHz */
|
||||
{ 4, 0 },
|
||||
|
||||
/* ARM L5: 1000 MHz */
|
||||
{ 4, 0 },
|
||||
|
||||
/* ARM L6: 900 MHz */
|
||||
{ 3, 0 },
|
||||
|
||||
/* ARM L7: 800 MHz */
|
||||
{ 3, 0 },
|
||||
|
||||
/* ARM L8: 700 MHz */
|
||||
{ 3, 0 },
|
||||
|
||||
/* ARM L9: 600 MHz */
|
||||
{ 3, 0 },
|
||||
|
||||
/* ARM L10: 500 MHz */
|
||||
{ 3, 0 },
|
||||
|
||||
/* ARM L11: 400 MHz */
|
||||
{ 3, 0 },
|
||||
|
||||
/* ARM L12: 300 MHz */
|
||||
{ 3, 0 },
|
||||
|
||||
/* ARM L13: 200 MHz */
|
||||
{ 3, 0 },
|
||||
};
|
||||
|
||||
static unsigned int clkdiv_cpu1_4412[CPUFREQ_LEVEL_END][3] = {
|
||||
/* Clock divider value for following
|
||||
* { DIVCOPY, DIVHPM, DIVCORES }
|
||||
*/
|
||||
/* ARM L0: 1500 MHz */
|
||||
{ 6, 0, 7 },
|
||||
|
||||
/* ARM L1: 1400 MHz */
|
||||
{ 6, 0, 6 },
|
||||
|
||||
/* ARM L2: 1300 MHz */
|
||||
{ 5, 0, 6 },
|
||||
|
||||
/* ARM L3: 1200 MHz */
|
||||
{ 5, 0, 5 },
|
||||
|
||||
/* ARM L4: 1100 MHz */
|
||||
{ 4, 0, 5 },
|
||||
|
||||
/* ARM L5: 1000 MHz */
|
||||
{ 4, 0, 4 },
|
||||
|
||||
/* ARM L6: 900 MHz */
|
||||
{ 3, 0, 4 },
|
||||
|
||||
/* ARM L7: 800 MHz */
|
||||
{ 3, 0, 3 },
|
||||
|
||||
/* ARM L8: 700 MHz */
|
||||
{ 3, 0, 3 },
|
||||
|
||||
/* ARM L9: 600 MHz */
|
||||
{ 3, 0, 2 },
|
||||
|
||||
/* ARM L10: 500 MHz */
|
||||
{ 3, 0, 2 },
|
||||
|
||||
/* ARM L11: 400 MHz */
|
||||
{ 3, 0, 1 },
|
||||
|
||||
/* ARM L12: 300 MHz */
|
||||
{ 3, 0, 1 },
|
||||
|
||||
/* ARM L13: 200 MHz */
|
||||
{ 3, 0, 0 },
|
||||
};
|
||||
|
||||
static unsigned int exynos4x12_apll_pms_table[CPUFREQ_LEVEL_END] = {
|
||||
/* APLL FOUT L0: 1500 MHz */
|
||||
((250 << 16) | (4 << 8) | (0x0)),
|
||||
|
||||
/* APLL FOUT L1: 1400 MHz */
|
||||
((175 << 16) | (3 << 8) | (0x0)),
|
||||
|
||||
/* APLL FOUT L2: 1300 MHz */
|
||||
((325 << 16) | (6 << 8) | (0x0)),
|
||||
|
||||
/* APLL FOUT L3: 1200 MHz */
|
||||
((200 << 16) | (4 << 8) | (0x0)),
|
||||
|
||||
/* APLL FOUT L4: 1100 MHz */
|
||||
((275 << 16) | (6 << 8) | (0x0)),
|
||||
|
||||
/* APLL FOUT L5: 1000 MHz */
|
||||
((125 << 16) | (3 << 8) | (0x0)),
|
||||
|
||||
/* APLL FOUT L6: 900 MHz */
|
||||
((150 << 16) | (4 << 8) | (0x0)),
|
||||
|
||||
/* APLL FOUT L7: 800 MHz */
|
||||
((100 << 16) | (3 << 8) | (0x0)),
|
||||
|
||||
/* APLL FOUT L8: 700 MHz */
|
||||
((175 << 16) | (3 << 8) | (0x1)),
|
||||
|
||||
/* APLL FOUT L9: 600 MHz */
|
||||
((200 << 16) | (4 << 8) | (0x1)),
|
||||
|
||||
/* APLL FOUT L10: 500 MHz */
|
||||
((125 << 16) | (3 << 8) | (0x1)),
|
||||
|
||||
/* APLL FOUT L11 400 MHz */
|
||||
((100 << 16) | (3 << 8) | (0x1)),
|
||||
|
||||
/* APLL FOUT L12: 300 MHz */
|
||||
((200 << 16) | (4 << 8) | (0x2)),
|
||||
|
||||
/* APLL FOUT L13: 200 MHz */
|
||||
((100 << 16) | (3 << 8) | (0x2)),
|
||||
};
|
||||
|
||||
static const unsigned int asv_voltage_4x12[CPUFREQ_LEVEL_END] = {
|
||||
1350000, 1287500, 1250000, 1187500, 1137500, 1087500, 1037500,
|
||||
1000000, 987500, 975000, 950000, 925000, 900000, 900000
|
||||
};
|
||||
|
||||
static void exynos4x12_set_clkdiv(unsigned int div_index)
|
||||
{
|
||||
unsigned int tmp;
|
||||
unsigned int stat_cpu1;
|
||||
|
||||
/* Change Divider - CPU0 */
|
||||
|
||||
tmp = exynos4x12_clkdiv_table[div_index].clkdiv;
|
||||
|
||||
__raw_writel(tmp, EXYNOS4_CLKDIV_CPU);
|
||||
|
||||
while (__raw_readl(EXYNOS4_CLKDIV_STATCPU) & 0x11111111)
|
||||
cpu_relax();
|
||||
|
||||
/* Change Divider - CPU1 */
|
||||
tmp = exynos4x12_clkdiv_table[div_index].clkdiv1;
|
||||
|
||||
__raw_writel(tmp, EXYNOS4_CLKDIV_CPU1);
|
||||
if (soc_is_exynos4212())
|
||||
stat_cpu1 = 0x11;
|
||||
else
|
||||
stat_cpu1 = 0x111;
|
||||
|
||||
while (__raw_readl(EXYNOS4_CLKDIV_STATCPU1) & stat_cpu1)
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
static void exynos4x12_set_apll(unsigned int index)
|
||||
{
|
||||
unsigned int tmp, pdiv;
|
||||
|
||||
/* 1. MUX_CORE_SEL = MPLL, ARMCLK uses MPLL for lock time */
|
||||
clk_set_parent(moutcore, mout_mpll);
|
||||
|
||||
do {
|
||||
cpu_relax();
|
||||
tmp = (__raw_readl(EXYNOS4_CLKMUX_STATCPU)
|
||||
>> EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT);
|
||||
tmp &= 0x7;
|
||||
} while (tmp != 0x2);
|
||||
|
||||
/* 2. Set APLL Lock time */
|
||||
pdiv = ((exynos4x12_apll_pms_table[index] >> 8) & 0x3f);
|
||||
|
||||
__raw_writel((pdiv * 250), EXYNOS4_APLL_LOCK);
|
||||
|
||||
/* 3. Change PLL PMS values */
|
||||
tmp = __raw_readl(EXYNOS4_APLL_CON0);
|
||||
tmp &= ~((0x3ff << 16) | (0x3f << 8) | (0x7 << 0));
|
||||
tmp |= exynos4x12_apll_pms_table[index];
|
||||
__raw_writel(tmp, EXYNOS4_APLL_CON0);
|
||||
|
||||
/* 4. wait_lock_time */
|
||||
do {
|
||||
cpu_relax();
|
||||
tmp = __raw_readl(EXYNOS4_APLL_CON0);
|
||||
} while (!(tmp & (0x1 << EXYNOS4_APLLCON0_LOCKED_SHIFT)));
|
||||
|
||||
/* 5. MUX_CORE_SEL = APLL */
|
||||
clk_set_parent(moutcore, mout_apll);
|
||||
|
||||
do {
|
||||
cpu_relax();
|
||||
tmp = __raw_readl(EXYNOS4_CLKMUX_STATCPU);
|
||||
tmp &= EXYNOS4_CLKMUX_STATCPU_MUXCORE_MASK;
|
||||
} while (tmp != (0x1 << EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT));
|
||||
}
|
||||
|
||||
bool exynos4x12_pms_change(unsigned int old_index, unsigned int new_index)
|
||||
{
|
||||
unsigned int old_pm = exynos4x12_apll_pms_table[old_index] >> 8;
|
||||
unsigned int new_pm = exynos4x12_apll_pms_table[new_index] >> 8;
|
||||
|
||||
return (old_pm == new_pm) ? 0 : 1;
|
||||
}
|
||||
|
||||
static void exynos4x12_set_frequency(unsigned int old_index,
|
||||
unsigned int new_index)
|
||||
{
|
||||
unsigned int tmp;
|
||||
|
||||
if (old_index > new_index) {
|
||||
if (!exynos4x12_pms_change(old_index, new_index)) {
|
||||
/* 1. Change the system clock divider values */
|
||||
exynos4x12_set_clkdiv(new_index);
|
||||
/* 2. Change just s value in apll m,p,s value */
|
||||
tmp = __raw_readl(EXYNOS4_APLL_CON0);
|
||||
tmp &= ~(0x7 << 0);
|
||||
tmp |= (exynos4x12_apll_pms_table[new_index] & 0x7);
|
||||
__raw_writel(tmp, EXYNOS4_APLL_CON0);
|
||||
|
||||
} else {
|
||||
/* Clock Configuration Procedure */
|
||||
/* 1. Change the system clock divider values */
|
||||
exynos4x12_set_clkdiv(new_index);
|
||||
/* 2. Change the apll m,p,s value */
|
||||
exynos4x12_set_apll(new_index);
|
||||
}
|
||||
} else if (old_index < new_index) {
|
||||
if (!exynos4x12_pms_change(old_index, new_index)) {
|
||||
/* 1. Change just s value in apll m,p,s value */
|
||||
tmp = __raw_readl(EXYNOS4_APLL_CON0);
|
||||
tmp &= ~(0x7 << 0);
|
||||
tmp |= (exynos4x12_apll_pms_table[new_index] & 0x7);
|
||||
__raw_writel(tmp, EXYNOS4_APLL_CON0);
|
||||
/* 2. Change the system clock divider values */
|
||||
exynos4x12_set_clkdiv(new_index);
|
||||
} else {
|
||||
/* Clock Configuration Procedure */
|
||||
/* 1. Change the apll m,p,s value */
|
||||
exynos4x12_set_apll(new_index);
|
||||
/* 2. Change the system clock divider values */
|
||||
exynos4x12_set_clkdiv(new_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void __init set_volt_table(void)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
max_support_idx = L1;
|
||||
|
||||
/* Not supported */
|
||||
exynos4x12_freq_table[L0].frequency = CPUFREQ_ENTRY_INVALID;
|
||||
|
||||
for (i = 0 ; i < CPUFREQ_LEVEL_END ; i++)
|
||||
exynos4x12_volt_table[i] = asv_voltage_4x12[i];
|
||||
}
|
||||
|
||||
int exynos4x12_cpufreq_init(struct exynos_dvfs_info *info)
|
||||
{
|
||||
int i;
|
||||
unsigned int tmp;
|
||||
unsigned long rate;
|
||||
|
||||
set_volt_table();
|
||||
|
||||
cpu_clk = clk_get(NULL, "armclk");
|
||||
if (IS_ERR(cpu_clk))
|
||||
return PTR_ERR(cpu_clk);
|
||||
|
||||
moutcore = clk_get(NULL, "moutcore");
|
||||
if (IS_ERR(moutcore))
|
||||
goto err_moutcore;
|
||||
|
||||
mout_mpll = clk_get(NULL, "mout_mpll");
|
||||
if (IS_ERR(mout_mpll))
|
||||
goto err_mout_mpll;
|
||||
|
||||
rate = clk_get_rate(mout_mpll) / 1000;
|
||||
|
||||
mout_apll = clk_get(NULL, "mout_apll");
|
||||
if (IS_ERR(mout_apll))
|
||||
goto err_mout_apll;
|
||||
|
||||
for (i = L0; i < CPUFREQ_LEVEL_END; i++) {
|
||||
|
||||
exynos4x12_clkdiv_table[i].index = i;
|
||||
|
||||
tmp = __raw_readl(EXYNOS4_CLKDIV_CPU);
|
||||
|
||||
tmp &= ~(EXYNOS4_CLKDIV_CPU0_CORE_MASK |
|
||||
EXYNOS4_CLKDIV_CPU0_COREM0_MASK |
|
||||
EXYNOS4_CLKDIV_CPU0_COREM1_MASK |
|
||||
EXYNOS4_CLKDIV_CPU0_PERIPH_MASK |
|
||||
EXYNOS4_CLKDIV_CPU0_ATB_MASK |
|
||||
EXYNOS4_CLKDIV_CPU0_PCLKDBG_MASK |
|
||||
EXYNOS4_CLKDIV_CPU0_APLL_MASK);
|
||||
|
||||
if (soc_is_exynos4212()) {
|
||||
tmp |= ((clkdiv_cpu0_4212[i][0] << EXYNOS4_CLKDIV_CPU0_CORE_SHIFT) |
|
||||
(clkdiv_cpu0_4212[i][1] << EXYNOS4_CLKDIV_CPU0_COREM0_SHIFT) |
|
||||
(clkdiv_cpu0_4212[i][2] << EXYNOS4_CLKDIV_CPU0_COREM1_SHIFT) |
|
||||
(clkdiv_cpu0_4212[i][3] << EXYNOS4_CLKDIV_CPU0_PERIPH_SHIFT) |
|
||||
(clkdiv_cpu0_4212[i][4] << EXYNOS4_CLKDIV_CPU0_ATB_SHIFT) |
|
||||
(clkdiv_cpu0_4212[i][5] << EXYNOS4_CLKDIV_CPU0_PCLKDBG_SHIFT) |
|
||||
(clkdiv_cpu0_4212[i][6] << EXYNOS4_CLKDIV_CPU0_APLL_SHIFT));
|
||||
} else {
|
||||
tmp &= ~EXYNOS4_CLKDIV_CPU0_CORE2_MASK;
|
||||
|
||||
tmp |= ((clkdiv_cpu0_4412[i][0] << EXYNOS4_CLKDIV_CPU0_CORE_SHIFT) |
|
||||
(clkdiv_cpu0_4412[i][1] << EXYNOS4_CLKDIV_CPU0_COREM0_SHIFT) |
|
||||
(clkdiv_cpu0_4412[i][2] << EXYNOS4_CLKDIV_CPU0_COREM1_SHIFT) |
|
||||
(clkdiv_cpu0_4412[i][3] << EXYNOS4_CLKDIV_CPU0_PERIPH_SHIFT) |
|
||||
(clkdiv_cpu0_4412[i][4] << EXYNOS4_CLKDIV_CPU0_ATB_SHIFT) |
|
||||
(clkdiv_cpu0_4412[i][5] << EXYNOS4_CLKDIV_CPU0_PCLKDBG_SHIFT) |
|
||||
(clkdiv_cpu0_4412[i][6] << EXYNOS4_CLKDIV_CPU0_APLL_SHIFT) |
|
||||
(clkdiv_cpu0_4412[i][7] << EXYNOS4_CLKDIV_CPU0_CORE2_SHIFT));
|
||||
}
|
||||
|
||||
exynos4x12_clkdiv_table[i].clkdiv = tmp;
|
||||
|
||||
tmp = __raw_readl(EXYNOS4_CLKDIV_CPU1);
|
||||
|
||||
if (soc_is_exynos4212()) {
|
||||
tmp &= ~(EXYNOS4_CLKDIV_CPU1_COPY_MASK |
|
||||
EXYNOS4_CLKDIV_CPU1_HPM_MASK);
|
||||
tmp |= ((clkdiv_cpu1_4212[i][0] << EXYNOS4_CLKDIV_CPU1_COPY_SHIFT) |
|
||||
(clkdiv_cpu1_4212[i][1] << EXYNOS4_CLKDIV_CPU1_HPM_SHIFT));
|
||||
} else {
|
||||
tmp &= ~(EXYNOS4_CLKDIV_CPU1_COPY_MASK |
|
||||
EXYNOS4_CLKDIV_CPU1_HPM_MASK |
|
||||
EXYNOS4_CLKDIV_CPU1_CORES_MASK);
|
||||
tmp |= ((clkdiv_cpu1_4412[i][0] << EXYNOS4_CLKDIV_CPU1_COPY_SHIFT) |
|
||||
(clkdiv_cpu1_4412[i][1] << EXYNOS4_CLKDIV_CPU1_HPM_SHIFT) |
|
||||
(clkdiv_cpu1_4412[i][2] << EXYNOS4_CLKDIV_CPU1_CORES_SHIFT));
|
||||
}
|
||||
exynos4x12_clkdiv_table[i].clkdiv1 = tmp;
|
||||
}
|
||||
|
||||
info->mpll_freq_khz = rate;
|
||||
info->pm_lock_idx = L5;
|
||||
info->pll_safe_idx = L7;
|
||||
info->max_support_idx = max_support_idx;
|
||||
info->min_support_idx = min_support_idx;
|
||||
info->cpu_clk = cpu_clk;
|
||||
info->volt_table = exynos4x12_volt_table;
|
||||
info->freq_table = exynos4x12_freq_table;
|
||||
info->set_freq = exynos4x12_set_frequency;
|
||||
info->need_apll_change = exynos4x12_pms_change;
|
||||
|
||||
return 0;
|
||||
|
||||
err_mout_apll:
|
||||
clk_put(mout_mpll);
|
||||
err_mout_mpll:
|
||||
clk_put(moutcore);
|
||||
err_moutcore:
|
||||
clk_put(cpu_clk);
|
||||
|
||||
pr_debug("%s: failed initialization\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL(exynos4x12_cpufreq_init);
|
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
* Copyright (c) 2010-20122Samsung Electronics Co., Ltd.
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* EXYNOS5250 - CPU frequency scaling support
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/cpufreq.h>
|
||||
|
||||
#include <mach/map.h>
|
||||
#include <mach/regs-clock.h>
|
||||
#include <mach/cpufreq.h>
|
||||
|
||||
#define CPUFREQ_LEVEL_END (L15 + 1)
|
||||
|
||||
static int max_support_idx;
|
||||
static int min_support_idx = (CPUFREQ_LEVEL_END - 1);
|
||||
static struct clk *cpu_clk;
|
||||
static struct clk *moutcore;
|
||||
static struct clk *mout_mpll;
|
||||
static struct clk *mout_apll;
|
||||
|
||||
struct cpufreq_clkdiv {
|
||||
unsigned int index;
|
||||
unsigned int clkdiv;
|
||||
unsigned int clkdiv1;
|
||||
};
|
||||
|
||||
static unsigned int exynos5250_volt_table[CPUFREQ_LEVEL_END];
|
||||
|
||||
static struct cpufreq_frequency_table exynos5250_freq_table[] = {
|
||||
{L0, 1700 * 1000},
|
||||
{L1, 1600 * 1000},
|
||||
{L2, 1500 * 1000},
|
||||
{L3, 1400 * 1000},
|
||||
{L4, 1300 * 1000},
|
||||
{L5, 1200 * 1000},
|
||||
{L6, 1100 * 1000},
|
||||
{L7, 1000 * 1000},
|
||||
{L8, 900 * 1000},
|
||||
{L9, 800 * 1000},
|
||||
{L10, 700 * 1000},
|
||||
{L11, 600 * 1000},
|
||||
{L12, 500 * 1000},
|
||||
{L13, 400 * 1000},
|
||||
{L14, 300 * 1000},
|
||||
{L15, 200 * 1000},
|
||||
{0, CPUFREQ_TABLE_END},
|
||||
};
|
||||
|
||||
static struct cpufreq_clkdiv exynos5250_clkdiv_table[CPUFREQ_LEVEL_END];
|
||||
|
||||
static unsigned int clkdiv_cpu0_5250[CPUFREQ_LEVEL_END][8] = {
|
||||
/*
|
||||
* Clock divider value for following
|
||||
* { ARM, CPUD, ACP, PERIPH, ATB, PCLK_DBG, APLL, ARM2 }
|
||||
*/
|
||||
{ 0, 3, 7, 7, 6, 1, 3, 0 }, /* 1700 MHz - N/A */
|
||||
{ 0, 3, 7, 7, 6, 1, 3, 0 }, /* 1600 MHz - N/A */
|
||||
{ 0, 3, 7, 7, 5, 1, 3, 0 }, /* 1500 MHz - N/A */
|
||||
{ 0, 3, 7, 7, 6, 1, 3, 0 }, /* 1400 MHz */
|
||||
{ 0, 3, 7, 7, 6, 1, 3, 0 }, /* 1300 MHz */
|
||||
{ 0, 3, 7, 7, 5, 1, 3, 0 }, /* 1200 MHz */
|
||||
{ 0, 2, 7, 7, 5, 1, 2, 0 }, /* 1100 MHz */
|
||||
{ 0, 2, 7, 7, 4, 1, 2, 0 }, /* 1000 MHz */
|
||||
{ 0, 2, 7, 7, 4, 1, 2, 0 }, /* 900 MHz */
|
||||
{ 0, 2, 7, 7, 3, 1, 1, 0 }, /* 800 MHz */
|
||||
{ 0, 1, 7, 7, 3, 1, 1, 0 }, /* 700 MHz */
|
||||
{ 0, 1, 7, 7, 2, 1, 1, 0 }, /* 600 MHz */
|
||||
{ 0, 1, 7, 7, 2, 1, 1, 0 }, /* 500 MHz */
|
||||
{ 0, 1, 7, 7, 1, 1, 1, 0 }, /* 400 MHz */
|
||||
{ 0, 1, 7, 7, 1, 1, 1, 0 }, /* 300 MHz */
|
||||
{ 0, 1, 7, 7, 1, 1, 1, 0 }, /* 200 MHz */
|
||||
};
|
||||
|
||||
static unsigned int clkdiv_cpu1_5250[CPUFREQ_LEVEL_END][2] = {
|
||||
/* Clock divider value for following
|
||||
* { COPY, HPM }
|
||||
*/
|
||||
{ 0, 2 }, /* 1700 MHz - N/A */
|
||||
{ 0, 2 }, /* 1600 MHz - N/A */
|
||||
{ 0, 2 }, /* 1500 MHz - N/A */
|
||||
{ 0, 2 }, /* 1400 MHz */
|
||||
{ 0, 2 }, /* 1300 MHz */
|
||||
{ 0, 2 }, /* 1200 MHz */
|
||||
{ 0, 2 }, /* 1100 MHz */
|
||||
{ 0, 2 }, /* 1000 MHz */
|
||||
{ 0, 2 }, /* 900 MHz */
|
||||
{ 0, 2 }, /* 800 MHz */
|
||||
{ 0, 2 }, /* 700 MHz */
|
||||
{ 0, 2 }, /* 600 MHz */
|
||||
{ 0, 2 }, /* 500 MHz */
|
||||
{ 0, 2 }, /* 400 MHz */
|
||||
{ 0, 2 }, /* 300 MHz */
|
||||
{ 0, 2 }, /* 200 MHz */
|
||||
};
|
||||
|
||||
static unsigned int exynos5_apll_pms_table[CPUFREQ_LEVEL_END] = {
|
||||
(0), /* 1700 MHz - N/A */
|
||||
(0), /* 1600 MHz - N/A */
|
||||
(0), /* 1500 MHz - N/A */
|
||||
(0), /* 1400 MHz */
|
||||
((325 << 16) | (6 << 8) | 0), /* 1300 MHz */
|
||||
((200 << 16) | (4 << 8) | 0), /* 1200 MHz */
|
||||
((275 << 16) | (6 << 8) | 0), /* 1100 MHz */
|
||||
((125 << 16) | (3 << 8) | 0), /* 1000 MHz */
|
||||
((150 << 16) | (4 << 8) | 0), /* 900 MHz */
|
||||
((100 << 16) | (3 << 8) | 0), /* 800 MHz */
|
||||
((175 << 16) | (3 << 8) | 1), /* 700 MHz */
|
||||
((200 << 16) | (4 << 8) | 1), /* 600 MHz */
|
||||
((125 << 16) | (3 << 8) | 1), /* 500 MHz */
|
||||
((100 << 16) | (3 << 8) | 1), /* 400 MHz */
|
||||
((200 << 16) | (4 << 8) | 2), /* 300 MHz */
|
||||
((100 << 16) | (3 << 8) | 2), /* 200 MHz */
|
||||
};
|
||||
|
||||
/* ASV group voltage table */
|
||||
static const unsigned int asv_voltage_5250[CPUFREQ_LEVEL_END] = {
|
||||
0, 0, 0, 0, 0, 0, 0, /* 1700 MHz ~ 1100 MHz Not supported */
|
||||
1175000, 1125000, 1075000, 1050000, 1000000,
|
||||
950000, 925000, 925000, 900000
|
||||
};
|
||||
|
||||
static void set_clkdiv(unsigned int div_index)
|
||||
{
|
||||
unsigned int tmp;
|
||||
|
||||
/* Change Divider - CPU0 */
|
||||
|
||||
tmp = exynos5250_clkdiv_table[div_index].clkdiv;
|
||||
|
||||
__raw_writel(tmp, EXYNOS5_CLKDIV_CPU0);
|
||||
|
||||
while (__raw_readl(EXYNOS5_CLKDIV_STATCPU0) & 0x11111111)
|
||||
cpu_relax();
|
||||
|
||||
/* Change Divider - CPU1 */
|
||||
tmp = exynos5250_clkdiv_table[div_index].clkdiv1;
|
||||
|
||||
__raw_writel(tmp, EXYNOS5_CLKDIV_CPU1);
|
||||
|
||||
while (__raw_readl(EXYNOS5_CLKDIV_STATCPU1) & 0x11)
|
||||
cpu_relax();
|
||||
}
|
||||
|
||||
static void set_apll(unsigned int new_index,
|
||||
unsigned int old_index)
|
||||
{
|
||||
unsigned int tmp, pdiv;
|
||||
|
||||
/* 1. MUX_CORE_SEL = MPLL, ARMCLK uses MPLL for lock time */
|
||||
clk_set_parent(moutcore, mout_mpll);
|
||||
|
||||
do {
|
||||
cpu_relax();
|
||||
tmp = (__raw_readl(EXYNOS5_CLKMUX_STATCPU) >> 16);
|
||||
tmp &= 0x7;
|
||||
} while (tmp != 0x2);
|
||||
|
||||
/* 2. Set APLL Lock time */
|
||||
pdiv = ((exynos5_apll_pms_table[new_index] >> 8) & 0x3f);
|
||||
|
||||
__raw_writel((pdiv * 250), EXYNOS5_APLL_LOCK);
|
||||
|
||||
/* 3. Change PLL PMS values */
|
||||
tmp = __raw_readl(EXYNOS5_APLL_CON0);
|
||||
tmp &= ~((0x3ff << 16) | (0x3f << 8) | (0x7 << 0));
|
||||
tmp |= exynos5_apll_pms_table[new_index];
|
||||
__raw_writel(tmp, EXYNOS5_APLL_CON0);
|
||||
|
||||
/* 4. wait_lock_time */
|
||||
do {
|
||||
cpu_relax();
|
||||
tmp = __raw_readl(EXYNOS5_APLL_CON0);
|
||||
} while (!(tmp & (0x1 << 29)));
|
||||
|
||||
/* 5. MUX_CORE_SEL = APLL */
|
||||
clk_set_parent(moutcore, mout_apll);
|
||||
|
||||
do {
|
||||
cpu_relax();
|
||||
tmp = __raw_readl(EXYNOS5_CLKMUX_STATCPU);
|
||||
tmp &= (0x7 << 16);
|
||||
} while (tmp != (0x1 << 16));
|
||||
|
||||
}
|
||||
|
||||
bool exynos5250_pms_change(unsigned int old_index, unsigned int new_index)
|
||||
{
|
||||
unsigned int old_pm = (exynos5_apll_pms_table[old_index] >> 8);
|
||||
unsigned int new_pm = (exynos5_apll_pms_table[new_index] >> 8);
|
||||
|
||||
return (old_pm == new_pm) ? 0 : 1;
|
||||
}
|
||||
|
||||
static void exynos5250_set_frequency(unsigned int old_index,
|
||||
unsigned int new_index)
|
||||
{
|
||||
unsigned int tmp;
|
||||
|
||||
if (old_index > new_index) {
|
||||
if (!exynos5250_pms_change(old_index, new_index)) {
|
||||
/* 1. Change the system clock divider values */
|
||||
set_clkdiv(new_index);
|
||||
/* 2. Change just s value in apll m,p,s value */
|
||||
tmp = __raw_readl(EXYNOS5_APLL_CON0);
|
||||
tmp &= ~(0x7 << 0);
|
||||
tmp |= (exynos5_apll_pms_table[new_index] & 0x7);
|
||||
__raw_writel(tmp, EXYNOS5_APLL_CON0);
|
||||
|
||||
} else {
|
||||
/* Clock Configuration Procedure */
|
||||
/* 1. Change the system clock divider values */
|
||||
set_clkdiv(new_index);
|
||||
/* 2. Change the apll m,p,s value */
|
||||
set_apll(new_index, old_index);
|
||||
}
|
||||
} else if (old_index < new_index) {
|
||||
if (!exynos5250_pms_change(old_index, new_index)) {
|
||||
/* 1. Change just s value in apll m,p,s value */
|
||||
tmp = __raw_readl(EXYNOS5_APLL_CON0);
|
||||
tmp &= ~(0x7 << 0);
|
||||
tmp |= (exynos5_apll_pms_table[new_index] & 0x7);
|
||||
__raw_writel(tmp, EXYNOS5_APLL_CON0);
|
||||
/* 2. Change the system clock divider values */
|
||||
set_clkdiv(new_index);
|
||||
} else {
|
||||
/* Clock Configuration Procedure */
|
||||
/* 1. Change the apll m,p,s value */
|
||||
set_apll(new_index, old_index);
|
||||
/* 2. Change the system clock divider values */
|
||||
set_clkdiv(new_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void __init set_volt_table(void)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
exynos5250_freq_table[L0].frequency = CPUFREQ_ENTRY_INVALID;
|
||||
exynos5250_freq_table[L1].frequency = CPUFREQ_ENTRY_INVALID;
|
||||
exynos5250_freq_table[L2].frequency = CPUFREQ_ENTRY_INVALID;
|
||||
exynos5250_freq_table[L3].frequency = CPUFREQ_ENTRY_INVALID;
|
||||
exynos5250_freq_table[L4].frequency = CPUFREQ_ENTRY_INVALID;
|
||||
exynos5250_freq_table[L5].frequency = CPUFREQ_ENTRY_INVALID;
|
||||
exynos5250_freq_table[L6].frequency = CPUFREQ_ENTRY_INVALID;
|
||||
|
||||
max_support_idx = L7;
|
||||
|
||||
for (i = 0 ; i < CPUFREQ_LEVEL_END ; i++)
|
||||
exynos5250_volt_table[i] = asv_voltage_5250[i];
|
||||
}
|
||||
|
||||
int exynos5250_cpufreq_init(struct exynos_dvfs_info *info)
|
||||
{
|
||||
int i;
|
||||
unsigned int tmp;
|
||||
unsigned long rate;
|
||||
|
||||
set_volt_table();
|
||||
|
||||
cpu_clk = clk_get(NULL, "armclk");
|
||||
if (IS_ERR(cpu_clk))
|
||||
return PTR_ERR(cpu_clk);
|
||||
|
||||
moutcore = clk_get(NULL, "mout_cpu");
|
||||
if (IS_ERR(moutcore))
|
||||
goto err_moutcore;
|
||||
|
||||
mout_mpll = clk_get(NULL, "mout_mpll");
|
||||
if (IS_ERR(mout_mpll))
|
||||
goto err_mout_mpll;
|
||||
|
||||
rate = clk_get_rate(mout_mpll) / 1000;
|
||||
|
||||
mout_apll = clk_get(NULL, "mout_apll");
|
||||
if (IS_ERR(mout_apll))
|
||||
goto err_mout_apll;
|
||||
|
||||
for (i = L0; i < CPUFREQ_LEVEL_END; i++) {
|
||||
|
||||
exynos5250_clkdiv_table[i].index = i;
|
||||
|
||||
tmp = __raw_readl(EXYNOS5_CLKDIV_CPU0);
|
||||
|
||||
tmp &= ~((0x7 << 0) | (0x7 << 4) | (0x7 << 8) |
|
||||
(0x7 << 12) | (0x7 << 16) | (0x7 << 20) |
|
||||
(0x7 << 24) | (0x7 << 28));
|
||||
|
||||
tmp |= ((clkdiv_cpu0_5250[i][0] << 0) |
|
||||
(clkdiv_cpu0_5250[i][1] << 4) |
|
||||
(clkdiv_cpu0_5250[i][2] << 8) |
|
||||
(clkdiv_cpu0_5250[i][3] << 12) |
|
||||
(clkdiv_cpu0_5250[i][4] << 16) |
|
||||
(clkdiv_cpu0_5250[i][5] << 20) |
|
||||
(clkdiv_cpu0_5250[i][6] << 24) |
|
||||
(clkdiv_cpu0_5250[i][7] << 28));
|
||||
|
||||
exynos5250_clkdiv_table[i].clkdiv = tmp;
|
||||
|
||||
tmp = __raw_readl(EXYNOS5_CLKDIV_CPU1);
|
||||
|
||||
tmp &= ~((0x7 << 0) | (0x7 << 4));
|
||||
|
||||
tmp |= ((clkdiv_cpu1_5250[i][0] << 0) |
|
||||
(clkdiv_cpu1_5250[i][1] << 4));
|
||||
|
||||
exynos5250_clkdiv_table[i].clkdiv1 = tmp;
|
||||
}
|
||||
|
||||
info->mpll_freq_khz = rate;
|
||||
/* 1000Mhz */
|
||||
info->pm_lock_idx = L7;
|
||||
/* 800Mhz */
|
||||
info->pll_safe_idx = L9;
|
||||
info->max_support_idx = max_support_idx;
|
||||
info->min_support_idx = min_support_idx;
|
||||
info->cpu_clk = cpu_clk;
|
||||
info->volt_table = exynos5250_volt_table;
|
||||
info->freq_table = exynos5250_freq_table;
|
||||
info->set_freq = exynos5250_set_frequency;
|
||||
info->need_apll_change = exynos5250_pms_change;
|
||||
|
||||
return 0;
|
||||
|
||||
err_mout_apll:
|
||||
clk_put(mout_mpll);
|
||||
err_mout_mpll:
|
||||
clk_put(moutcore);
|
||||
err_moutcore:
|
||||
clk_put(cpu_clk);
|
||||
|
||||
pr_err("%s: failed initialization\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL(exynos5250_cpufreq_init);
|
|
@ -0,0 +1,542 @@
|
|||
/*
|
||||
* S3C2416/2450 CPUfreq Support
|
||||
*
|
||||
* Copyright 2011 Heiko Stuebner <heiko@sntech.de>
|
||||
*
|
||||
* based on s3c64xx_cpufreq.c
|
||||
*
|
||||
* Copyright 2009 Wolfson Microelectronics plc
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/cpufreq.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
static DEFINE_MUTEX(cpufreq_lock);
|
||||
|
||||
struct s3c2416_data {
|
||||
struct clk *armdiv;
|
||||
struct clk *armclk;
|
||||
struct clk *hclk;
|
||||
|
||||
unsigned long regulator_latency;
|
||||
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
|
||||
struct regulator *vddarm;
|
||||
#endif
|
||||
|
||||
struct cpufreq_frequency_table *freq_table;
|
||||
|
||||
bool is_dvs;
|
||||
bool disable_dvs;
|
||||
};
|
||||
|
||||
static struct s3c2416_data s3c2416_cpufreq;
|
||||
|
||||
struct s3c2416_dvfs {
|
||||
unsigned int vddarm_min;
|
||||
unsigned int vddarm_max;
|
||||
};
|
||||
|
||||
/* pseudo-frequency for dvs mode */
|
||||
#define FREQ_DVS 132333
|
||||
|
||||
/* frequency to sleep and reboot in
|
||||
* it's essential to leave dvs, as some boards do not reconfigure the
|
||||
* regulator on reboot
|
||||
*/
|
||||
#define FREQ_SLEEP 133333
|
||||
|
||||
/* Sources for the ARMCLK */
|
||||
#define SOURCE_HCLK 0
|
||||
#define SOURCE_ARMDIV 1
|
||||
|
||||
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
|
||||
/* S3C2416 only supports changing the voltage in the dvs-mode.
|
||||
* Voltages down to 1.0V seem to work, so we take what the regulator
|
||||
* can get us.
|
||||
*/
|
||||
static struct s3c2416_dvfs s3c2416_dvfs_table[] = {
|
||||
[SOURCE_HCLK] = { 950000, 1250000 },
|
||||
[SOURCE_ARMDIV] = { 1250000, 1350000 },
|
||||
};
|
||||
#endif
|
||||
|
||||
static struct cpufreq_frequency_table s3c2416_freq_table[] = {
|
||||
{ SOURCE_HCLK, FREQ_DVS },
|
||||
{ SOURCE_ARMDIV, 133333 },
|
||||
{ SOURCE_ARMDIV, 266666 },
|
||||
{ SOURCE_ARMDIV, 400000 },
|
||||
{ 0, CPUFREQ_TABLE_END },
|
||||
};
|
||||
|
||||
static struct cpufreq_frequency_table s3c2450_freq_table[] = {
|
||||
{ SOURCE_HCLK, FREQ_DVS },
|
||||
{ SOURCE_ARMDIV, 133500 },
|
||||
{ SOURCE_ARMDIV, 267000 },
|
||||
{ SOURCE_ARMDIV, 534000 },
|
||||
{ 0, CPUFREQ_TABLE_END },
|
||||
};
|
||||
|
||||
static int s3c2416_cpufreq_verify_speed(struct cpufreq_policy *policy)
|
||||
{
|
||||
struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
|
||||
|
||||
if (policy->cpu != 0)
|
||||
return -EINVAL;
|
||||
|
||||
return cpufreq_frequency_table_verify(policy, s3c_freq->freq_table);
|
||||
}
|
||||
|
||||
static unsigned int s3c2416_cpufreq_get_speed(unsigned int cpu)
|
||||
{
|
||||
struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
|
||||
|
||||
if (cpu != 0)
|
||||
return 0;
|
||||
|
||||
/* return our pseudo-frequency when in dvs mode */
|
||||
if (s3c_freq->is_dvs)
|
||||
return FREQ_DVS;
|
||||
|
||||
return clk_get_rate(s3c_freq->armclk) / 1000;
|
||||
}
|
||||
|
||||
static int s3c2416_cpufreq_set_armdiv(struct s3c2416_data *s3c_freq,
|
||||
unsigned int freq)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (clk_get_rate(s3c_freq->armdiv) / 1000 != freq) {
|
||||
ret = clk_set_rate(s3c_freq->armdiv, freq * 1000);
|
||||
if (ret < 0) {
|
||||
pr_err("cpufreq: Failed to set armdiv rate %dkHz: %d\n",
|
||||
freq, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c2416_cpufreq_enter_dvs(struct s3c2416_data *s3c_freq, int idx)
|
||||
{
|
||||
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
|
||||
struct s3c2416_dvfs *dvfs;
|
||||
#endif
|
||||
int ret;
|
||||
|
||||
if (s3c_freq->is_dvs) {
|
||||
pr_debug("cpufreq: already in dvs mode, nothing to do\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
pr_debug("cpufreq: switching armclk to hclk (%lukHz)\n",
|
||||
clk_get_rate(s3c_freq->hclk) / 1000);
|
||||
ret = clk_set_parent(s3c_freq->armclk, s3c_freq->hclk);
|
||||
if (ret < 0) {
|
||||
pr_err("cpufreq: Failed to switch armclk to hclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
|
||||
/* changing the core voltage is only allowed when in dvs mode */
|
||||
if (s3c_freq->vddarm) {
|
||||
dvfs = &s3c2416_dvfs_table[idx];
|
||||
|
||||
pr_debug("cpufreq: setting regultor to %d-%d\n",
|
||||
dvfs->vddarm_min, dvfs->vddarm_max);
|
||||
ret = regulator_set_voltage(s3c_freq->vddarm,
|
||||
dvfs->vddarm_min,
|
||||
dvfs->vddarm_max);
|
||||
|
||||
/* when lowering the voltage failed, there is nothing to do */
|
||||
if (ret != 0)
|
||||
pr_err("cpufreq: Failed to set VDDARM: %d\n", ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
s3c_freq->is_dvs = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c2416_cpufreq_leave_dvs(struct s3c2416_data *s3c_freq, int idx)
|
||||
{
|
||||
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
|
||||
struct s3c2416_dvfs *dvfs;
|
||||
#endif
|
||||
int ret;
|
||||
|
||||
if (!s3c_freq->is_dvs) {
|
||||
pr_debug("cpufreq: not in dvs mode, so can't leave\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
|
||||
if (s3c_freq->vddarm) {
|
||||
dvfs = &s3c2416_dvfs_table[idx];
|
||||
|
||||
pr_debug("cpufreq: setting regultor to %d-%d\n",
|
||||
dvfs->vddarm_min, dvfs->vddarm_max);
|
||||
ret = regulator_set_voltage(s3c_freq->vddarm,
|
||||
dvfs->vddarm_min,
|
||||
dvfs->vddarm_max);
|
||||
if (ret != 0) {
|
||||
pr_err("cpufreq: Failed to set VDDARM: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* force armdiv to hclk frequency for transition from dvs*/
|
||||
if (clk_get_rate(s3c_freq->armdiv) > clk_get_rate(s3c_freq->hclk)) {
|
||||
pr_debug("cpufreq: force armdiv to hclk frequency (%lukHz)\n",
|
||||
clk_get_rate(s3c_freq->hclk) / 1000);
|
||||
ret = s3c2416_cpufreq_set_armdiv(s3c_freq,
|
||||
clk_get_rate(s3c_freq->hclk) / 1000);
|
||||
if (ret < 0) {
|
||||
pr_err("cpufreq: Failed to to set the armdiv to %lukHz: %d\n",
|
||||
clk_get_rate(s3c_freq->hclk) / 1000, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
pr_debug("cpufreq: switching armclk parent to armdiv (%lukHz)\n",
|
||||
clk_get_rate(s3c_freq->armdiv) / 1000);
|
||||
|
||||
ret = clk_set_parent(s3c_freq->armclk, s3c_freq->armdiv);
|
||||
if (ret < 0) {
|
||||
pr_err("cpufreq: Failed to switch armclk clock parent to armdiv: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
s3c_freq->is_dvs = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c2416_cpufreq_set_target(struct cpufreq_policy *policy,
|
||||
unsigned int target_freq,
|
||||
unsigned int relation)
|
||||
{
|
||||
struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
|
||||
struct cpufreq_freqs freqs;
|
||||
int idx, ret, to_dvs = 0;
|
||||
unsigned int i;
|
||||
|
||||
mutex_lock(&cpufreq_lock);
|
||||
|
||||
pr_debug("cpufreq: to %dKHz, relation %d\n", target_freq, relation);
|
||||
|
||||
ret = cpufreq_frequency_table_target(policy, s3c_freq->freq_table,
|
||||
target_freq, relation, &i);
|
||||
if (ret != 0)
|
||||
goto out;
|
||||
|
||||
idx = s3c_freq->freq_table[i].index;
|
||||
|
||||
if (idx == SOURCE_HCLK)
|
||||
to_dvs = 1;
|
||||
|
||||
/* switching to dvs when it's not allowed */
|
||||
if (to_dvs && s3c_freq->disable_dvs) {
|
||||
pr_debug("cpufreq: entering dvs mode not allowed\n");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
freqs.cpu = 0;
|
||||
freqs.flags = 0;
|
||||
freqs.old = s3c_freq->is_dvs ? FREQ_DVS
|
||||
: clk_get_rate(s3c_freq->armclk) / 1000;
|
||||
|
||||
/* When leavin dvs mode, always switch the armdiv to the hclk rate
|
||||
* The S3C2416 has stability issues when switching directly to
|
||||
* higher frequencies.
|
||||
*/
|
||||
freqs.new = (s3c_freq->is_dvs && !to_dvs)
|
||||
? clk_get_rate(s3c_freq->hclk) / 1000
|
||||
: s3c_freq->freq_table[i].frequency;
|
||||
|
||||
pr_debug("cpufreq: Transition %d-%dkHz\n", freqs.old, freqs.new);
|
||||
|
||||
if (!to_dvs && freqs.old == freqs.new)
|
||||
goto out;
|
||||
|
||||
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
|
||||
|
||||
if (to_dvs) {
|
||||
pr_debug("cpufreq: enter dvs\n");
|
||||
ret = s3c2416_cpufreq_enter_dvs(s3c_freq, idx);
|
||||
} else if (s3c_freq->is_dvs) {
|
||||
pr_debug("cpufreq: leave dvs\n");
|
||||
ret = s3c2416_cpufreq_leave_dvs(s3c_freq, idx);
|
||||
} else {
|
||||
pr_debug("cpufreq: change armdiv to %dkHz\n", freqs.new);
|
||||
ret = s3c2416_cpufreq_set_armdiv(s3c_freq, freqs.new);
|
||||
}
|
||||
|
||||
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
|
||||
|
||||
out:
|
||||
mutex_unlock(&cpufreq_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
|
||||
static void __init s3c2416_cpufreq_cfg_regulator(struct s3c2416_data *s3c_freq)
|
||||
{
|
||||
int count, v, i, found;
|
||||
struct cpufreq_frequency_table *freq;
|
||||
struct s3c2416_dvfs *dvfs;
|
||||
|
||||
count = regulator_count_voltages(s3c_freq->vddarm);
|
||||
if (count < 0) {
|
||||
pr_err("cpufreq: Unable to check supported voltages\n");
|
||||
return;
|
||||
}
|
||||
|
||||
freq = s3c_freq->freq_table;
|
||||
while (count > 0 && freq->frequency != CPUFREQ_TABLE_END) {
|
||||
if (freq->frequency == CPUFREQ_ENTRY_INVALID)
|
||||
continue;
|
||||
|
||||
dvfs = &s3c2416_dvfs_table[freq->index];
|
||||
found = 0;
|
||||
|
||||
/* Check only the min-voltage, more is always ok on S3C2416 */
|
||||
for (i = 0; i < count; i++) {
|
||||
v = regulator_list_voltage(s3c_freq->vddarm, i);
|
||||
if (v >= dvfs->vddarm_min)
|
||||
found = 1;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
pr_debug("cpufreq: %dkHz unsupported by regulator\n",
|
||||
freq->frequency);
|
||||
freq->frequency = CPUFREQ_ENTRY_INVALID;
|
||||
}
|
||||
|
||||
freq++;
|
||||
}
|
||||
|
||||
/* Guessed */
|
||||
s3c_freq->regulator_latency = 1 * 1000 * 1000;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int s3c2416_cpufreq_reboot_notifier_evt(struct notifier_block *this,
|
||||
unsigned long event, void *ptr)
|
||||
{
|
||||
struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&cpufreq_lock);
|
||||
|
||||
/* disable further changes */
|
||||
s3c_freq->disable_dvs = 1;
|
||||
|
||||
mutex_unlock(&cpufreq_lock);
|
||||
|
||||
/* some boards don't reconfigure the regulator on reboot, which
|
||||
* could lead to undervolting the cpu when the clock is reset.
|
||||
* Therefore we always leave the DVS mode on reboot.
|
||||
*/
|
||||
if (s3c_freq->is_dvs) {
|
||||
pr_debug("cpufreq: leave dvs on reboot\n");
|
||||
ret = cpufreq_driver_target(cpufreq_cpu_get(0), FREQ_SLEEP, 0);
|
||||
if (ret < 0)
|
||||
return NOTIFY_BAD;
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static struct notifier_block s3c2416_cpufreq_reboot_notifier = {
|
||||
.notifier_call = s3c2416_cpufreq_reboot_notifier_evt,
|
||||
};
|
||||
|
||||
static int __init s3c2416_cpufreq_driver_init(struct cpufreq_policy *policy)
|
||||
{
|
||||
struct s3c2416_data *s3c_freq = &s3c2416_cpufreq;
|
||||
struct cpufreq_frequency_table *freq;
|
||||
struct clk *msysclk;
|
||||
unsigned long rate;
|
||||
int ret;
|
||||
|
||||
if (policy->cpu != 0)
|
||||
return -EINVAL;
|
||||
|
||||
msysclk = clk_get(NULL, "msysclk");
|
||||
if (IS_ERR(msysclk)) {
|
||||
ret = PTR_ERR(msysclk);
|
||||
pr_err("cpufreq: Unable to obtain msysclk: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* S3C2416 and S3C2450 share the same processor-ID and also provide no
|
||||
* other means to distinguish them other than through the rate of
|
||||
* msysclk. On S3C2416 msysclk runs at 800MHz and on S3C2450 at 533MHz.
|
||||
*/
|
||||
rate = clk_get_rate(msysclk);
|
||||
if (rate == 800 * 1000 * 1000) {
|
||||
pr_info("cpufreq: msysclk running at %lukHz, using S3C2416 frequency table\n",
|
||||
rate / 1000);
|
||||
s3c_freq->freq_table = s3c2416_freq_table;
|
||||
policy->cpuinfo.max_freq = 400000;
|
||||
} else if (rate / 1000 == 534000) {
|
||||
pr_info("cpufreq: msysclk running at %lukHz, using S3C2450 frequency table\n",
|
||||
rate / 1000);
|
||||
s3c_freq->freq_table = s3c2450_freq_table;
|
||||
policy->cpuinfo.max_freq = 534000;
|
||||
}
|
||||
|
||||
/* not needed anymore */
|
||||
clk_put(msysclk);
|
||||
|
||||
if (s3c_freq->freq_table == NULL) {
|
||||
pr_err("cpufreq: No frequency information for this CPU, msysclk at %lukHz\n",
|
||||
rate / 1000);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
s3c_freq->is_dvs = 0;
|
||||
|
||||
s3c_freq->armdiv = clk_get(NULL, "armdiv");
|
||||
if (IS_ERR(s3c_freq->armdiv)) {
|
||||
ret = PTR_ERR(s3c_freq->armdiv);
|
||||
pr_err("cpufreq: Unable to obtain ARMDIV: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
s3c_freq->hclk = clk_get(NULL, "hclk");
|
||||
if (IS_ERR(s3c_freq->hclk)) {
|
||||
ret = PTR_ERR(s3c_freq->hclk);
|
||||
pr_err("cpufreq: Unable to obtain HCLK: %d\n", ret);
|
||||
goto err_hclk;
|
||||
}
|
||||
|
||||
/* chech hclk rate, we only support the common 133MHz for now
|
||||
* hclk could also run at 66MHz, but this not often used
|
||||
*/
|
||||
rate = clk_get_rate(s3c_freq->hclk);
|
||||
if (rate < 133 * 1000 * 1000) {
|
||||
pr_err("cpufreq: HCLK not at 133MHz\n");
|
||||
clk_put(s3c_freq->hclk);
|
||||
ret = -EINVAL;
|
||||
goto err_armclk;
|
||||
}
|
||||
|
||||
s3c_freq->armclk = clk_get(NULL, "armclk");
|
||||
if (IS_ERR(s3c_freq->armclk)) {
|
||||
ret = PTR_ERR(s3c_freq->armclk);
|
||||
pr_err("cpufreq: Unable to obtain ARMCLK: %d\n", ret);
|
||||
goto err_armclk;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
|
||||
s3c_freq->vddarm = regulator_get(NULL, "vddarm");
|
||||
if (IS_ERR(s3c_freq->vddarm)) {
|
||||
ret = PTR_ERR(s3c_freq->vddarm);
|
||||
pr_err("cpufreq: Failed to obtain VDDARM: %d\n", ret);
|
||||
goto err_vddarm;
|
||||
}
|
||||
|
||||
s3c2416_cpufreq_cfg_regulator(s3c_freq);
|
||||
#else
|
||||
s3c_freq->regulator_latency = 0;
|
||||
#endif
|
||||
|
||||
freq = s3c_freq->freq_table;
|
||||
while (freq->frequency != CPUFREQ_TABLE_END) {
|
||||
/* special handling for dvs mode */
|
||||
if (freq->index == 0) {
|
||||
if (!s3c_freq->hclk) {
|
||||
pr_debug("cpufreq: %dkHz unsupported as it would need unavailable dvs mode\n",
|
||||
freq->frequency);
|
||||
freq->frequency = CPUFREQ_ENTRY_INVALID;
|
||||
} else {
|
||||
freq++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for frequencies we can generate */
|
||||
rate = clk_round_rate(s3c_freq->armdiv,
|
||||
freq->frequency * 1000);
|
||||
rate /= 1000;
|
||||
if (rate != freq->frequency) {
|
||||
pr_debug("cpufreq: %dkHz unsupported by clock (clk_round_rate return %lu)\n",
|
||||
freq->frequency, rate);
|
||||
freq->frequency = CPUFREQ_ENTRY_INVALID;
|
||||
}
|
||||
|
||||
freq++;
|
||||
}
|
||||
|
||||
policy->cur = clk_get_rate(s3c_freq->armclk) / 1000;
|
||||
|
||||
/* Datasheet says PLL stabalisation time must be at least 300us,
|
||||
* so but add some fudge. (reference in LOCKCON0 register description)
|
||||
*/
|
||||
policy->cpuinfo.transition_latency = (500 * 1000) +
|
||||
s3c_freq->regulator_latency;
|
||||
|
||||
ret = cpufreq_frequency_table_cpuinfo(policy, s3c_freq->freq_table);
|
||||
if (ret)
|
||||
goto err_freq_table;
|
||||
|
||||
cpufreq_frequency_table_get_attr(s3c_freq->freq_table, 0);
|
||||
|
||||
register_reboot_notifier(&s3c2416_cpufreq_reboot_notifier);
|
||||
|
||||
return 0;
|
||||
|
||||
err_freq_table:
|
||||
#ifdef CONFIG_ARM_S3C2416_CPUFREQ_VCORESCALE
|
||||
regulator_put(s3c_freq->vddarm);
|
||||
err_vddarm:
|
||||
#endif
|
||||
clk_put(s3c_freq->armclk);
|
||||
err_armclk:
|
||||
clk_put(s3c_freq->hclk);
|
||||
err_hclk:
|
||||
clk_put(s3c_freq->armdiv);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct freq_attr *s3c2416_cpufreq_attr[] = {
|
||||
&cpufreq_freq_attr_scaling_available_freqs,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct cpufreq_driver s3c2416_cpufreq_driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.flags = 0,
|
||||
.verify = s3c2416_cpufreq_verify_speed,
|
||||
.target = s3c2416_cpufreq_set_target,
|
||||
.get = s3c2416_cpufreq_get_speed,
|
||||
.init = s3c2416_cpufreq_driver_init,
|
||||
.name = "s3c2416",
|
||||
.attr = s3c2416_cpufreq_attr,
|
||||
};
|
||||
|
||||
static int __init s3c2416_cpufreq_init(void)
|
||||
{
|
||||
return cpufreq_register_driver(&s3c2416_cpufreq_driver);
|
||||
}
|
||||
module_init(s3c2416_cpufreq_init);
|
|
@ -217,13 +217,6 @@ static int s3c64xx_cpufreq_driver_init(struct cpufreq_policy *policy)
|
|||
} else {
|
||||
s3c64xx_cpufreq_config_regulator();
|
||||
}
|
||||
|
||||
vddint = regulator_get(NULL, "vddint");
|
||||
if (IS_ERR(vddint)) {
|
||||
ret = PTR_ERR(vddint);
|
||||
pr_err("Failed to obtain VDDINT: %d\n", ret);
|
||||
vddint = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
freq = s3c64xx_freq_table;
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#ifdef CONFIG_CPU_FREQ
|
||||
int cpufreq_register_notifier(struct notifier_block *nb, unsigned int list);
|
||||
int cpufreq_unregister_notifier(struct notifier_block *nb, unsigned int list);
|
||||
extern void disable_cpufreq(void);
|
||||
#else /* CONFIG_CPU_FREQ */
|
||||
static inline int cpufreq_register_notifier(struct notifier_block *nb,
|
||||
unsigned int list)
|
||||
|
@ -46,6 +47,7 @@ static inline int cpufreq_unregister_notifier(struct notifier_block *nb,
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void disable_cpufreq(void) { }
|
||||
#endif /* CONFIG_CPU_FREQ */
|
||||
|
||||
/* if (cpufreq_driver->target) exists, the ->governor decides what frequency
|
||||
|
|
Loading…
Reference in New Issue