1
0
Fork 0

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
Linus Torvalds 2012-03-23 17:56:39 -07:00
commit cf821923ba
12 changed files with 1590 additions and 42 deletions

View File

@ -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 *);

View File

@ -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.

View File

@ -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
##################################################################################

View File

@ -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));

View File

@ -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;
}

View File

@ -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__);

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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