aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrey Konovalov <andrey.konovalov@linaro.org>2012-05-22 20:39:00 +0400
committerAndrey Konovalov <andrey.konovalov@linaro.org>2012-05-22 20:39:00 +0400
commit0f832ec0abce4400dffafe77927722b7c65c4377 (patch)
tree6bc06b8bc1bbd1cc6c76b96db47eb7d168761695
parentf59c68d96739439b1e84553425be9c9f5fc99866 (diff)
parentfd43b478a7679a31658300b047c38448dfc5ead8 (diff)
Merge branch 'rebase-samslt-asv_cpufreq' into merge-linux-linaro
-rw-r--r--arch/arm/mach-exynos/Makefile1
-rw-r--r--arch/arm/mach-exynos/asv-4210.c339
-rw-r--r--arch/arm/mach-exynos/asv.c71
-rw-r--r--arch/arm/mach-exynos/clock-exynos4.c35
-rw-r--r--arch/arm/mach-exynos/include/mach/asv.h43
-rw-r--r--arch/arm/mach-exynos/include/mach/cpufreq.h2
-rw-r--r--arch/arm/mach-exynos/include/mach/map.h2
-rw-r--r--arch/arm/mach-exynos/include/mach/regs-clock.h18
-rw-r--r--arch/arm/mach-exynos/include/mach/regs-iem.h27
-rw-r--r--drivers/cpufreq/exynos-cpufreq.c62
-rw-r--r--drivers/cpufreq/exynos4210-cpufreq.c61
11 files changed, 650 insertions, 11 deletions
diff --git a/arch/arm/mach-exynos/Makefile b/arch/arm/mach-exynos/Makefile
index b4fd5349423..a0555cffb31 100644
--- a/arch/arm/mach-exynos/Makefile
+++ b/arch/arm/mach-exynos/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_SOC_EXYNOS4212) += clock-exynos4212.o
obj-$(CONFIG_PM) += pm.o
obj-$(CONFIG_PM_GENERIC_DOMAINS) += pm_domains.o
obj-$(CONFIG_CPU_IDLE) += cpuidle.o
+obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += asv.o asv-4210.o
obj-$(CONFIG_ARCH_EXYNOS4) += pmu.o
diff --git a/arch/arm/mach-exynos/asv-4210.c b/arch/arm/mach-exynos/asv-4210.c
new file mode 100644
index 00000000000..8f08cb2898f
--- /dev/null
+++ b/arch/arm/mach-exynos/asv-4210.c
@@ -0,0 +1,339 @@
+/* linux/arch/arm/mach-exynos/asv-4210.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS4210 - ASV(Adaptive Support Voltage) driver
+ *
+ * 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/init.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+
+#include <plat/clock.h>
+
+#include <mach/regs-iem.h>
+#include <mach/regs-clock.h>
+#include <mach/asv.h>
+
+/*
+ * exynos_result_of_asv is result of ASV group.
+ * Using by this value, other driver can adjust voltage.
+ */
+unsigned int exynos_result_of_asv;
+
+enum target_asv {
+ EXYNOS4210_1200,
+ EXYNOS4210_1400,
+ EXYNOS4210_SINGLE_1200,
+};
+
+struct asv_judge_table exynos4210_1200_limit[] = {
+ /* HPM , IDS */
+ {8 , 4},
+ {11 , 8},
+ {14 , 12},
+ {18 , 17},
+ {21 , 27},
+ {23 , 45},
+ {25 , 55},
+};
+
+static struct asv_judge_table exynos4210_1400_limit[] = {
+ /* HPM , IDS */
+ {13 , 8},
+ {17 , 12},
+ {22 , 32},
+ {26 , 52},
+};
+
+static struct asv_judge_table exynos4210_single_1200_limit[] = {
+ /* HPM , IDS */
+ {8 , 4},
+ {14 , 12},
+ {21 , 27},
+ {25 , 55},
+};
+
+static int exynos4210_asv_pre_clock_init(void)
+{
+ struct clk *clk_hpm;
+ struct clk *clk_copy;
+ struct clk *clk_parent;
+
+ /* PWI clock setting */
+ clk_copy = clk_get(NULL, "sclk_pwi");
+ if (IS_ERR(clk_copy))
+ goto clock_fail;
+ else {
+ clk_parent = clk_get(NULL, "xusbxti");
+
+ if (IS_ERR(clk_parent)) {
+ clk_put(clk_copy);
+
+ goto clock_fail;
+ }
+ if (clk_set_parent(clk_copy, clk_parent))
+ goto clock_fail;
+
+ clk_put(clk_parent);
+ }
+ clk_set_rate(clk_copy, (48 * MHZ));
+
+ clk_put(clk_copy);
+
+ /* HPM clock setting */
+ clk_copy = clk_get(NULL, "dout_copy");
+ if (IS_ERR(clk_copy))
+ goto clock_fail;
+ else {
+ clk_parent = clk_get(NULL, "mout_mpll");
+ if (IS_ERR(clk_parent)) {
+ clk_put(clk_copy);
+
+ goto clock_fail;
+ }
+ if (clk_set_parent(clk_copy, clk_parent))
+ goto clock_fail;
+
+ clk_put(clk_parent);
+ }
+
+ clk_set_rate(clk_copy, (400 * MHZ));
+
+ clk_put(clk_copy);
+
+ clk_hpm = clk_get(NULL, "sclk_hpm");
+ if (IS_ERR(clk_hpm))
+ goto clock_fail;
+
+ clk_set_rate(clk_hpm, (200 * MHZ));
+
+ clk_put(clk_hpm);
+
+ return 0;
+
+clock_fail:
+ pr_err("EXYNOS4210: ASV: Clock init fail\n");
+
+ return -EBUSY;
+}
+
+static int exynos4210_asv_pre_clock_setup(void)
+{
+ /* APLL_CON0 level register */
+ __raw_writel(0x80FA0601, EXYNOS4_APLL_CON0L8);
+ __raw_writel(0x80C80601, EXYNOS4_APLL_CON0L7);
+ __raw_writel(0x80C80602, EXYNOS4_APLL_CON0L6);
+ __raw_writel(0x80C80604, EXYNOS4_APLL_CON0L5);
+ __raw_writel(0x80C80601, EXYNOS4_APLL_CON0L4);
+ __raw_writel(0x80C80601, EXYNOS4_APLL_CON0L3);
+ __raw_writel(0x80C80601, EXYNOS4_APLL_CON0L2);
+ __raw_writel(0x80C80601, EXYNOS4_APLL_CON0L1);
+
+ /* IEM Divider register */
+ __raw_writel(0x00500000, EXYNOS4_CLKDIV_IEM_L8);
+ __raw_writel(0x00500000, EXYNOS4_CLKDIV_IEM_L7);
+ __raw_writel(0x00500000, EXYNOS4_CLKDIV_IEM_L6);
+ __raw_writel(0x00500000, EXYNOS4_CLKDIV_IEM_L5);
+ __raw_writel(0x00500000, EXYNOS4_CLKDIV_IEM_L4);
+ __raw_writel(0x00500000, EXYNOS4_CLKDIV_IEM_L3);
+ __raw_writel(0x00500000, EXYNOS4_CLKDIV_IEM_L2);
+ __raw_writel(0x00500000, EXYNOS4_CLKDIV_IEM_L1);
+
+ return 0;
+}
+
+static int exynos4210_find_group(struct samsung_asv *asv_info,
+ enum target_asv exynos4_target)
+{
+ unsigned int ret = 0;
+ unsigned int i;
+
+ if (exynos4_target == EXYNOS4210_1200) {
+ ret = ARRAY_SIZE(exynos4210_1200_limit);
+
+ for (i = 0; i < ARRAY_SIZE(exynos4210_1200_limit); i++) {
+ if (asv_info->hpm_result <= exynos4210_1200_limit[i].hpm_limit ||
+ asv_info->ids_result <= exynos4210_1200_limit[i].ids_limit) {
+ ret = i;
+ break;
+ }
+ }
+ } else if (exynos4_target == EXYNOS4210_1400) {
+ ret = ARRAY_SIZE(exynos4210_1400_limit);
+
+ for (i = 0; i < ARRAY_SIZE(exynos4210_1400_limit); i++) {
+ if (asv_info->hpm_result <= exynos4210_1400_limit[i].hpm_limit ||
+ asv_info->ids_result <= exynos4210_1400_limit[i].ids_limit) {
+ ret = i;
+ break;
+ }
+ }
+ } else if (exynos4_target == EXYNOS4210_SINGLE_1200) {
+ ret = ARRAY_SIZE(exynos4210_single_1200_limit);
+
+ for (i = 0; i < ARRAY_SIZE(exynos4210_single_1200_limit); i++) {
+ if (asv_info->hpm_result <= exynos4210_single_1200_limit[i].hpm_limit ||
+ asv_info->ids_result <= exynos4210_single_1200_limit[i].ids_limit) {
+ ret = i;
+ break;
+ }
+ }
+ }
+
+ return ret;
+}
+
+#define PACK_ID 8
+#define PACK_MASK 0x3
+
+#define SUPPORT_1400MHZ (1 << 31)
+#define SUPPORT_1200MHZ (1 << 30)
+#define SUPPORT_1000MHZ (1 << 29)
+
+static int exynos4210_get_hpm(struct samsung_asv *asv_info)
+{
+ unsigned int i;
+ unsigned int tmp;
+ unsigned int hpm_delay = 0;
+ void __iomem *iem_base;
+
+ iem_base = ioremap(EXYNOS4_PA_IEM, SZ_128K);
+
+ if (!iem_base) {
+ pr_err("EXYNOS4210: ASV: ioremap fail\n");
+ return -EPERM;
+ }
+
+ /* Clock setting to get asv value */
+ if (!asv_info->pre_clock_init)
+ goto err;
+ else {
+ if (asv_info->pre_clock_init())
+ goto err;
+ else {
+ /* HPM enable */
+ tmp = __raw_readl(iem_base + EXYNOS4_APC_CONTROL);
+ tmp |= APC_HPM_EN;
+ __raw_writel(tmp, (iem_base + EXYNOS4_APC_CONTROL));
+
+ asv_info->pre_clock_setup();
+
+ /* IEM enable */
+ tmp = __raw_readl(iem_base + EXYNOS4_IECDPCCR);
+ tmp |= IEC_EN;
+ __raw_writel(tmp, (iem_base + EXYNOS4_IECDPCCR));
+ }
+ }
+
+ /* Get HPM Delay value */
+ for (i = 0; i < EXYNOS4_LOOP_CNT; i++) {
+ tmp = __raw_readb(iem_base + EXYNOS4_APC_DBG_DLYCODE);
+ hpm_delay += tmp;
+ }
+
+ hpm_delay /= EXYNOS4_LOOP_CNT;
+
+ /* Store result of hpm value */
+ asv_info->hpm_result = hpm_delay;
+
+ return 0;
+
+err:
+ pr_err("EXYNOS4210: ASV: Failt to get hpm function\n");
+
+ iounmap(iem_base);
+
+ return -EPERM;
+}
+
+static int exynos4210_get_ids(struct samsung_asv *asv_info)
+{
+ unsigned int pkg_id_val;
+
+ if (!asv_info->ids_offset || !asv_info->ids_mask) {
+ pr_err("EXYNOS4210: ASV: No ids_offset or No ids_mask\n");
+
+ return -EPERM;
+ }
+
+ pkg_id_val = __raw_readl(S5P_VA_CHIPID + 0x4);
+ asv_info->pkg_id = pkg_id_val;
+ asv_info->ids_result = ((pkg_id_val >> asv_info->ids_offset) &
+ asv_info->ids_mask);
+
+ return 0;
+}
+
+static int exynos4210_asv_store_result(struct samsung_asv *asv_info)
+{
+ unsigned int result_grp;
+ char *support_freq;
+ unsigned int exynos_idcode = 0x0;
+
+ exynos_result_of_asv = 0;
+
+ exynos_idcode = __raw_readl(S5P_VA_CHIPID);
+
+ /* Single chip is only support 1.2GHz */
+ if (!((exynos_idcode >> PACK_ID) & PACK_MASK)) {
+ result_grp = exynos4210_find_group(asv_info, EXYNOS4210_SINGLE_1200);
+ result_grp |= SUPPORT_1200MHZ;
+ support_freq = "1.2GHz";
+
+ goto set_reg;
+ }
+
+ /* Check support freq */
+ switch (asv_info->pkg_id & 0x7) {
+ /* Support 1.2GHz */
+ case 1:
+ case 7:
+ result_grp = exynos4210_find_group(asv_info, EXYNOS4210_1200);
+ result_grp |= SUPPORT_1200MHZ;
+ support_freq = "1.2GHz";
+ break;
+ /* Support 1.4GHz */
+ case 5:
+ result_grp = exynos4210_find_group(asv_info, EXYNOS4210_1400);
+ result_grp |= SUPPORT_1200MHZ;
+ support_freq = "1.4GHz";
+ break;
+ /* Defalut support 1.0GHz */
+ default:
+ result_grp = exynos4210_find_group(asv_info, EXYNOS4210_1200);
+ result_grp |= SUPPORT_1000MHZ;
+ support_freq = "1.0GHz";
+ break;
+ }
+
+set_reg:
+ exynos_result_of_asv = result_grp;
+
+ pr_info("EXYNOS4: ASV: Support %s and Group is 0x%x\n",
+ support_freq, result_grp);
+
+ return 0;
+}
+
+void exynos4210_asv_init(struct samsung_asv *asv_info)
+{
+ pr_info("EXYNOS4210: Adaptive Support Voltage init\n");
+
+ asv_info->ids_offset = 24;
+ asv_info->ids_mask = 0xFF;
+
+ asv_info->get_ids = exynos4210_get_ids;
+ asv_info->get_hpm = exynos4210_get_hpm;
+ asv_info->pre_clock_init = exynos4210_asv_pre_clock_init;
+ asv_info->pre_clock_setup = exynos4210_asv_pre_clock_setup;
+ asv_info->store_result = exynos4210_asv_store_result;
+}
diff --git a/arch/arm/mach-exynos/asv.c b/arch/arm/mach-exynos/asv.c
new file mode 100644
index 00000000000..d0a33d3cecf
--- /dev/null
+++ b/arch/arm/mach-exynos/asv.c
@@ -0,0 +1,71 @@
+/* linux/arch/arm/mach-exynos/asv.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS4 - ASV(Adaptive Support Voltage) driver
+ *
+ * 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/init.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#include <plat/cpu.h>
+
+#include <mach/map.h>
+#include <mach/asv.h>
+
+static struct samsung_asv *exynos_asv;
+
+static int __init exynos_asv_init(void)
+{
+ exynos_asv = kzalloc(sizeof(struct samsung_asv), GFP_KERNEL);
+ if (!exynos_asv)
+ goto out;
+
+ if (soc_is_exynos4210())
+ exynos4210_asv_init(exynos_asv);
+ else
+ goto out;
+
+ if (exynos_asv->check_vdd_arm) {
+ if (exynos_asv->check_vdd_arm())
+ goto out;
+ }
+
+ /* Get HPM Delay value */
+ if (exynos_asv->get_hpm) {
+ if (exynos_asv->get_hpm(exynos_asv))
+ goto out;
+ } else
+ goto out;
+
+ /* Get IDS ARM Value */
+ if (exynos_asv->get_ids) {
+ if (exynos_asv->get_ids(exynos_asv))
+ goto out;
+ } else
+ goto out;
+
+ if (exynos_asv->store_result) {
+ if (exynos_asv->store_result(exynos_asv))
+ goto out;
+ } else
+ goto out;
+
+ return 0;
+out:
+ pr_err("EXYNOS : Fail to initialize ASV\n");
+
+ kfree(exynos_asv);
+
+ return -EINVAL;
+}
+device_initcall_sync(exynos_asv_init);
diff --git a/arch/arm/mach-exynos/clock-exynos4.c b/arch/arm/mach-exynos/clock-exynos4.c
index 288d06f98ff..bb40358d2f9 100644
--- a/arch/arm/mach-exynos/clock-exynos4.c
+++ b/arch/arm/mach-exynos/clock-exynos4.c
@@ -308,6 +308,25 @@ static struct clksrc_clk exynos4_clk_periphclk = {
.reg_div = { .reg = EXYNOS4_CLKDIV_CPU, .shift = 12, .size = 3 },
};
+static struct clk *exynos4_clkset_mout_hpm_list[] = {
+ [0] = &exynos4_clk_mout_apll.clk,
+ [1] = &exynos4_clk_mout_mpll.clk,
+};
+
+static struct clksrc_sources exynos4_clkset_sclk_hpm = {
+ .sources = exynos4_clkset_mout_hpm_list,
+ .nr_sources = ARRAY_SIZE(exynos4_clkset_mout_hpm_list),
+};
+
+static struct clksrc_clk exynos4_clk_dout_copy = {
+ .clk = {
+ .name = "dout_copy",
+ },
+ .sources = &exynos4_clkset_sclk_hpm,
+ .reg_src = { .reg = EXYNOS4_CLKSRC_CPU, .shift = 20, .size = 1 },
+ .reg_div = { .reg = EXYNOS4_CLKDIV_CPU1, .shift = 0, .size = 3 },
+};
+
/* Core list of CMU_CORE side */
static struct clk *exynos4_clkset_corebus_list[] = {
@@ -1152,7 +1171,20 @@ static struct clksrc_clk exynos4_clksrcs[] = {
.ctrlbit = (1 << 16),
},
.reg_div = { .reg = EXYNOS4_CLKDIV_FSYS3, .shift = 8, .size = 8 },
- }
+ }, {
+ .clk = {
+ .name = "sclk_hpm",
+ .parent = &exynos4_clk_dout_copy.clk,
+ },
+ .reg_div = { .reg = EXYNOS4_CLKDIV_CPU1, .shift = 4, .size = 3 },
+ }, {
+ .clk = {
+ .name = "sclk_pwi",
+ },
+ .sources = &exynos4_clkset_group,
+ .reg_src = { .reg = EXYNOS4_CLKSRC_DMC, .shift = 16, .size = 4 },
+ .reg_div = { .reg = EXYNOS4_CLKDIV_DMC1, .shift = 8, .size = 4 },
+ },
};
static struct clksrc_clk exynos4_clk_sclk_uart0 = {
@@ -1294,6 +1326,7 @@ static struct clksrc_clk *exynos4_sysclks[] = {
&exynos4_clk_armclk,
&exynos4_clk_aclk_corem0,
&exynos4_clk_aclk_cores,
+ &exynos4_clk_dout_copy,
&exynos4_clk_aclk_corem1,
&exynos4_clk_periphclk,
&exynos4_clk_mout_corebus,
diff --git a/arch/arm/mach-exynos/include/mach/asv.h b/arch/arm/mach-exynos/include/mach/asv.h
new file mode 100644
index 00000000000..5b48b74811e
--- /dev/null
+++ b/arch/arm/mach-exynos/include/mach/asv.h
@@ -0,0 +1,43 @@
+/* linux/arch/arm/mach-exynos/include/mach/asv.h
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS4 - Adaptive Support Voltage Header file
+ *
+ * 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.
+*/
+
+#ifndef __ASM_ARCH_ASV_H
+#define __ASM_ARCH_ASV_H __FILE__
+
+#define JUDGE_TABLE_END NULL
+#define EXYNOS4_LOOP_CNT 10
+
+struct asv_judge_table {
+ unsigned int hpm_limit; /* HPM value to decide group of target */
+ unsigned int ids_limit; /* IDS value to decide group of target */
+};
+
+struct samsung_asv {
+ unsigned int pkg_id; /* fused value for chip */
+ unsigned int ids_offset; /* ids_offset of chip */
+ unsigned int ids_mask; /* ids_mask of chip */
+ unsigned int hpm_result; /* hpm value of chip */
+ unsigned int ids_result; /* ids value of chip */
+ int (*check_vdd_arm)(void); /* check vdd_arm value, this function is selectable */
+ int (*pre_clock_init)(void); /* clock init function to get hpm */
+ int (*pre_clock_setup)(void); /* clock setup function to get hpm */
+ /* specific get ids function */
+ int (*get_ids)(struct samsung_asv *asv_info);
+ /* specific get hpm function */
+ int (*get_hpm)(struct samsung_asv *asv_info);
+ /* store into some repository to send result of asv */
+ int (*store_result)(struct samsung_asv *asv_info);
+};
+
+extern void exynos4210_asv_init(struct samsung_asv *asv_info);
+
+#endif /* __ASM_ARCH_ASV_H */
diff --git a/arch/arm/mach-exynos/include/mach/cpufreq.h b/arch/arm/mach-exynos/include/mach/cpufreq.h
index 7517c3f417a..be80017eace 100644
--- a/arch/arm/mach-exynos/include/mach/cpufreq.h
+++ b/arch/arm/mach-exynos/include/mach/cpufreq.h
@@ -10,6 +10,8 @@
* published by the Free Software Foundation.
*/
+extern unsigned int exynos_result_of_asv;
+
enum cpufreq_level_index {
L0, L1, L2, L3, L4,
L5, L6, L7, L8, L9,
diff --git a/arch/arm/mach-exynos/include/mach/map.h b/arch/arm/mach-exynos/include/mach/map.h
index 4195089b7fc..32087c88f4a 100644
--- a/arch/arm/mach-exynos/include/mach/map.h
+++ b/arch/arm/mach-exynos/include/mach/map.h
@@ -76,6 +76,8 @@
#define EXYNOS4_PA_COMBINER 0x10440000
#define EXYNOS5_PA_COMBINER 0x10440000
+#define EXYNOS4_PA_IEM 0x10460000
+
#define EXYNOS4_PA_GIC_CPU 0x10480000
#define EXYNOS4_PA_GIC_DIST 0x10490000
#define EXYNOS5_PA_GIC_CPU 0x10480000
diff --git a/arch/arm/mach-exynos/include/mach/regs-clock.h b/arch/arm/mach-exynos/include/mach/regs-clock.h
index d9578a58ae7..cff0c718ffe 100644
--- a/arch/arm/mach-exynos/include/mach/regs-clock.h
+++ b/arch/arm/mach-exynos/include/mach/regs-clock.h
@@ -135,6 +135,24 @@
#define EXYNOS4_CLKGATE_SCLKCPU EXYNOS_CLKREG(0x14800)
#define EXYNOS4_CLKGATE_IP_CPU EXYNOS_CLKREG(0x14900)
+#define EXYNOS4_APLL_CON0L8 EXYNOS_CLKREG(0x15100)
+#define EXYNOS4_APLL_CON0L7 EXYNOS_CLKREG(0x15104)
+#define EXYNOS4_APLL_CON0L6 EXYNOS_CLKREG(0x15108)
+#define EXYNOS4_APLL_CON0L5 EXYNOS_CLKREG(0x1510C)
+#define EXYNOS4_APLL_CON0L4 EXYNOS_CLKREG(0x15110)
+#define EXYNOS4_APLL_CON0L3 EXYNOS_CLKREG(0x15114)
+#define EXYNOS4_APLL_CON0L2 EXYNOS_CLKREG(0x15118)
+#define EXYNOS4_APLL_CON0L1 EXYNOS_CLKREG(0x1511C)
+
+#define EXYNOS4_CLKDIV_IEM_L8 EXYNOS_CLKREG(0x15300)
+#define EXYNOS4_CLKDIV_IEM_L7 EXYNOS_CLKREG(0x15304)
+#define EXYNOS4_CLKDIV_IEM_L6 EXYNOS_CLKREG(0x15308)
+#define EXYNOS4_CLKDIV_IEM_L5 EXYNOS_CLKREG(0x1530C)
+#define EXYNOS4_CLKDIV_IEM_L4 EXYNOS_CLKREG(0x15310)
+#define EXYNOS4_CLKDIV_IEM_L3 EXYNOS_CLKREG(0x15314)
+#define EXYNOS4_CLKDIV_IEM_L2 EXYNOS_CLKREG(0x15318)
+#define EXYNOS4_CLKDIV_IEM_L1 EXYNOS_CLKREG(0x1531C)
+
#define EXYNOS4_APLL_LOCKTIME (0x1C20) /* 300us */
#define EXYNOS4_APLLCON0_ENABLE_SHIFT (31)
diff --git a/arch/arm/mach-exynos/include/mach/regs-iem.h b/arch/arm/mach-exynos/include/mach/regs-iem.h
new file mode 100644
index 00000000000..d9bf1777858
--- /dev/null
+++ b/arch/arm/mach-exynos/include/mach/regs-iem.h
@@ -0,0 +1,27 @@
+/* linux/arch/arm/mach-exynos/include/mach/regs-iem.h
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS4 - IEM(INTELLIGENT ENERGY MANAGEMENT) register discription
+ *
+ * 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.
+*/
+
+#ifndef __ASM_ARCH_REGS_IEM_H
+#define __ASM_ARCH_REGS_IEM_H __FILE__
+
+/* Register for IEC */
+#define EXYNOS4_IECDPCCR (0x00000)
+
+/* Register for APC */
+#define EXYNOS4_APC_CONTROL (0x10010)
+#define EXYNOS4_APC_PREDLYSEL (0x10024)
+#define EXYNOS4_APC_DBG_DLYCODE (0x100E0)
+
+#define APC_HPM_EN (1 << 4)
+#define IEC_EN (1 << 0)
+
+#endif /* __ASM_ARCH_REGS_IEM_H */
diff --git a/drivers/cpufreq/exynos-cpufreq.c b/drivers/cpufreq/exynos-cpufreq.c
index b243a7ee01f..1ff48a4526b 100644
--- a/drivers/cpufreq/exynos-cpufreq.c
+++ b/drivers/cpufreq/exynos-cpufreq.c
@@ -17,6 +17,8 @@
#include <linux/regulator/consumer.h>
#include <linux/cpufreq.h>
#include <linux/suspend.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
#include <mach/cpufreq.h>
@@ -46,7 +48,7 @@ static int exynos_target(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation)
{
- unsigned int index, old_index;
+ unsigned int index, old_index = 0, i;
unsigned int arm_volt, safe_arm_volt = 0;
int ret = 0;
struct cpufreq_frequency_table *freq_table = exynos_info->freq_table;
@@ -62,10 +64,11 @@ static int exynos_target(struct cpufreq_policy *policy,
goto out;
}
- if (cpufreq_frequency_table_target(policy, freq_table,
- freqs.old, relation, &old_index)) {
- ret = -EINVAL;
- goto out;
+ for (i = 0; (freq_table[i].frequency != CPUFREQ_TABLE_END); i++) {
+ if (freqs.old == freq_table[i].frequency) {
+ old_index = i;
+ break;
+ }
}
if (cpufreq_frequency_table_target(policy, freq_table,
@@ -204,8 +207,35 @@ static struct notifier_block exynos_cpufreq_nb = {
.notifier_call = exynos_cpufreq_pm_notifier,
};
+static int exynos_cpufreq_reboot_notifier(struct notifier_block *this,
+ unsigned long code, void *_cmd)
+{
+ struct cpufreq_policy *policy = cpufreq_cpu_get(0); /* boot CPU */
+ mutex_lock(&cpufreq_lock);
+
+ if (frequency_locked)
+ goto out;
+ frequency_locked = true;
+
+ if (locking_frequency) {
+ mutex_unlock(&cpufreq_lock);
+ exynos_target(policy, locking_frequency, CPUFREQ_RELATION_H);
+ mutex_lock(&cpufreq_lock);
+ }
+
+out:
+ mutex_unlock(&cpufreq_lock);
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block exynos_cpufreq_reboot_nb = {
+ .notifier_call = exynos_cpufreq_reboot_notifier,
+};
+
static int exynos_cpufreq_cpu_init(struct cpufreq_policy *policy)
{
+ int ret;
+
policy->cur = policy->min = policy->max = exynos_getspeed(policy->cpu);
cpufreq_frequency_table_get_attr(exynos_info->freq_table, policy->cpu);
@@ -228,16 +258,35 @@ static int exynos_cpufreq_cpu_init(struct cpufreq_policy *policy)
cpumask_setall(policy->cpus);
}
- return cpufreq_frequency_table_cpuinfo(policy, exynos_info->freq_table);
+ ret = cpufreq_frequency_table_cpuinfo(policy, exynos_info->freq_table);
+ if (ret)
+ return ret;
+
+ cpufreq_frequency_table_get_attr(exynos_info->freq_table, policy->cpu);
+ return 0;
+
}
+static int exynos4_cpufreq_cpu_exit(struct cpufreq_policy *policy)
+{
+ cpufreq_frequency_table_put_attr(policy->cpu);
+ return 0;
+}
+
+static struct freq_attr *exynos4_cpufreq_attr[] = {
+ &cpufreq_freq_attr_scaling_available_freqs,
+ NULL,
+};
+
static struct cpufreq_driver exynos_driver = {
.flags = CPUFREQ_STICKY,
.verify = exynos_verify_speed,
.target = exynos_target,
.get = exynos_getspeed,
.init = exynos_cpufreq_cpu_init,
+ .exit = exynos4_cpufreq_cpu_exit,
.name = "exynos_cpufreq",
+ .attr = exynos4_cpufreq_attr,
#ifdef CONFIG_PM
.suspend = exynos_cpufreq_suspend,
.resume = exynos_cpufreq_resume,
@@ -276,6 +325,7 @@ static int __init exynos_cpufreq_init(void)
}
register_pm_notifier(&exynos_cpufreq_nb);
+ register_reboot_notifier(&exynos_cpufreq_reboot_nb);
if (cpufreq_register_driver(&exynos_driver)) {
pr_err("%s: failed to register cpufreq driver\n", __func__);
diff --git a/drivers/cpufreq/exynos4210-cpufreq.c b/drivers/cpufreq/exynos4210-cpufreq.c
index fb148fa2767..e67453a6489 100644
--- a/drivers/cpufreq/exynos4210-cpufreq.c
+++ b/drivers/cpufreq/exynos4210-cpufreq.c
@@ -20,6 +20,13 @@
#include <mach/regs-clock.h>
#include <mach/cpufreq.h>
+#define SUPPORT_1400MHZ (1 << 31)
+#define SUPPORT_1200MHZ (1 << 30)
+#define SUPPORT_1000MHZ (1 << 29)
+
+#define SUPPORT_FREQ_SHIFT 29
+#define SUPPORT_FREQ_MASK 7
+
#define CPUFREQ_LEVEL_END L5
static int max_support_idx = L0;
@@ -35,10 +42,7 @@ struct cpufreq_clkdiv {
unsigned int clkdiv;
};
-static unsigned int exynos4210_volt_table[CPUFREQ_LEVEL_END] = {
- 1250000, 1150000, 1050000, 975000, 950000,
-};
-
+static unsigned int exynos4210_volt_table[CPUFREQ_LEVEL_END];
static struct cpufreq_clkdiv exynos4210_clkdiv_table[CPUFREQ_LEVEL_END];
@@ -51,6 +55,28 @@ static struct cpufreq_frequency_table exynos4210_freq_table[] = {
{0, CPUFREQ_TABLE_END},
};
+/*
+ * ASV group voltage table
+ */
+static const unsigned int asv_voltage[CPUFREQ_LEVEL_END][8] = {
+ /*
+ * SS, A1, A2, B1, B2, C1, C2, D
+ * @1200 :
+ * @1000 :
+ * @800 : ASV_VOLTAGE_TABLE
+ * @500 :
+ * @200 :
+ */
+ { 1350000, 1350000, 1300000, 1275000, 1250000, 1225000, 1200000,
+ 1175000 },
+ { 1300000, 1250000, 1200000, 1175000, 1150000, 1125000, 1100000,
+ 1075000 },
+ { 1200000, 1150000, 1100000, 1075000, 1050000, 1025000, 1000000,
+ 975000 },
+ { 1100000, 1050000, 1000000, 975000, 975000, 950000, 925000, 925000 },
+ { 1050000, 1000000, 975000, 950000, 950000, 925000, 925000, 925000 },
+};
+
static unsigned int clkdiv_cpu0[CPUFREQ_LEVEL_END][7] = {
/*
* Clock divider value for following
@@ -229,6 +255,31 @@ static void exynos4210_set_frequency(unsigned int old_index,
}
}
+static void __init set_volt_table(void)
+{
+ unsigned int tmp, i, asv_group = 0;
+
+ tmp = exynos_result_of_asv;
+
+ switch (tmp & (SUPPORT_FREQ_MASK << SUPPORT_FREQ_SHIFT)) {
+ case SUPPORT_1200MHZ:
+ asv_group = (tmp & 0xF);
+ break;
+ case SUPPORT_1400MHZ:
+ case SUPPORT_1000MHZ:
+ default:
+ /* Not supported and assign typical ASV group */
+ asv_group = 2;
+ break;
+ }
+
+ printk(KERN_INFO "DVFS: VDD_ARM Voltage table set with %d Group\n",
+ asv_group);
+
+ for (i = 0 ; i < CPUFREQ_LEVEL_END ; i++)
+ exynos4210_volt_table[i] = asv_voltage[i][asv_group];
+}
+
int exynos4210_cpufreq_init(struct exynos_dvfs_info *info)
{
int i;
@@ -275,6 +326,8 @@ int exynos4210_cpufreq_init(struct exynos_dvfs_info *info)
exynos4210_clkdiv_table[i].clkdiv = tmp;
}
+ set_volt_table();
+
info->mpll_freq_khz = rate;
info->pm_lock_idx = L2;
info->pll_safe_idx = L2;