From 34ee55075265d68ca858f2426e165733664385b4 Mon Sep 17 00:00:00 2001 From: Heiko Stübner Date: Thu, 16 Feb 2012 11:42:32 +0100 Subject: [CPUFREQ] Add S3C2416/S3C2450 cpufreq driver The S3C2416/S3C2450 SoCs support two sources for the armclk. The first source is the so called armdiv which divides the msysclk down to provide necessary cpu rates. In this mode the core voltage must be always at 1.3V. The frequency from the armdiv is not allowed to be lower than the hclk frequency. In the second mode the armclk can be sourced directly from the hclk in the so called "dynamic voltags scaling" (dvs) mode. Here the armdiv isn't used at all. Also in this mode the core voltage may be lowered. Existing hardware and tests with it suggest 1.0V as sufficient. When changing the clock source to the armdiv from the hclk, the SoC shows stability issues if the new frequency is higher than the current hclk frequency. Hence the driver always forces the armdiv to the hclk frequency before the source change and lets the cpufreq issue another set_target call for higher frequencies. To mark the hclk frequency as lower as the corresponding armdiv frequency it is set 1MHz below the real frequency. This lets the cpufreq framework change between 133MHz based on hclk and 133MHz based on armdiv at will. Signed-off-by: Heiko Stuebner Tested-by: Andrey Gusakov Signed-off-by: Dave Jones --- drivers/cpufreq/Makefile | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/cpufreq/Makefile') diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index ac000fa76bbb..042fbaeb140a 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -40,6 +40,7 @@ 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 -- cgit v1.2.3-59-g8ed1b From a35c50510dd4d20a9275a0d3d16b859d11b35606 Mon Sep 17 00:00:00 2001 From: Jaecheol Lee Date: Sat, 10 Mar 2012 02:59:22 -0800 Subject: EXYNOS4X12: Add support cpufreq for EXYNOS4X12 This patch adds support cpufreq for EXYNOS4X12 SoCs. Basically, the exynos-cpufreq.c is used commonly and exynos4x12-cpufreq.c is used for EXYNOS4212(two Cortex-A9 cores) and EXYNOS4412(four Cortex-A9 cores) SoCs. Signed-off-by: Jaecheol Lee Signed-off-by: Kukjin Kim Signed-off-by: Dave Jones --- arch/arm/mach-exynos/include/mach/cpufreq.h | 1 + drivers/cpufreq/Kconfig.arm | 7 + drivers/cpufreq/Makefile | 1 + drivers/cpufreq/exynos-cpufreq.c | 2 + drivers/cpufreq/exynos4x12-cpufreq.c | 536 ++++++++++++++++++++++++++++ 5 files changed, 547 insertions(+) create mode 100644 drivers/cpufreq/exynos4x12-cpufreq.c (limited to 'drivers/cpufreq/Makefile') diff --git a/arch/arm/mach-exynos/include/mach/cpufreq.h b/arch/arm/mach-exynos/include/mach/cpufreq.h index 3df27f2d5034..96ac0cbb91f8 100644 --- a/arch/arm/mach-exynos/include/mach/cpufreq.h +++ b/arch/arm/mach-exynos/include/mach/cpufreq.h @@ -32,3 +32,4 @@ struct exynos_dvfs_info { }; extern int exynos4210_cpufreq_init(struct exynos_dvfs_info *); +extern int exynos4x12_cpufreq_init(struct exynos_dvfs_info *); diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index dc59abf3360f..9b99a9672a57 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -47,6 +47,7 @@ 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) default y help This adds the CPUFreq driver common part for Samsung @@ -60,3 +61,9 @@ config ARM_EXYNOS4210_CPUFREQ 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). diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 042fbaeb140a..f37bbccbc879 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -45,6 +45,7 @@ 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_ARCH_OMAP2PLUS) += omap-cpufreq.o ################################################################################## diff --git a/drivers/cpufreq/exynos-cpufreq.c b/drivers/cpufreq/exynos-cpufreq.c index 7cfb51643555..2c63b68d5b23 100644 --- a/drivers/cpufreq/exynos-cpufreq.c +++ b/drivers/cpufreq/exynos-cpufreq.c @@ -254,6 +254,8 @@ 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 pr_err("%s: CPU type not found\n", __func__); diff --git a/drivers/cpufreq/exynos4x12-cpufreq.c b/drivers/cpufreq/exynos4x12-cpufreq.c new file mode 100644 index 000000000000..8c5a7afa5b0b --- /dev/null +++ b/drivers/cpufreq/exynos4x12-cpufreq.c @@ -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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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); -- cgit v1.2.3-59-g8ed1b From 562a6cbe1cf2edf0a87a659eb26095400322430e Mon Sep 17 00:00:00 2001 From: Jaecheol Lee Date: Sat, 10 Mar 2012 03:00:02 -0800 Subject: EXYNOS5250: Add support cpufreq for EXYNOS5250 This patch adds support cpufreq for EXYNOS5250 SoC. Basically, the exynos-cpufreq.c is used commonly and exynos5250-cpufreq.c is used for EXYNOS5250(two Cortex-A15 cores) SoC. Signed-off-by: Jaecheol Lee Signed-off-by: Kukjin Kim Signed-off-by: Dave Jones --- arch/arm/mach-exynos/include/mach/cpufreq.h | 1 + drivers/cpufreq/Kconfig.arm | 7 + drivers/cpufreq/Makefile | 1 + drivers/cpufreq/exynos-cpufreq.c | 2 + drivers/cpufreq/exynos5250-cpufreq.c | 347 ++++++++++++++++++++++++++++ 5 files changed, 358 insertions(+) create mode 100644 drivers/cpufreq/exynos5250-cpufreq.c (limited to 'drivers/cpufreq/Makefile') diff --git a/arch/arm/mach-exynos/include/mach/cpufreq.h b/arch/arm/mach-exynos/include/mach/cpufreq.h index 96ac0cbb91f8..7517c3f417af 100644 --- a/arch/arm/mach-exynos/include/mach/cpufreq.h +++ b/arch/arm/mach-exynos/include/mach/cpufreq.h @@ -33,3 +33,4 @@ 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 *); diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 9b99a9672a57..2e3351526ffa 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -48,6 +48,7 @@ config ARM_EXYNOS_CPUFREQ 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 @@ -67,3 +68,9 @@ config ARM_EXYNOS4X12_CPUFREQ 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. diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index f37bbccbc879..3cbbcd09c650 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -46,6 +46,7 @@ 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_ARCH_OMAP2PLUS) += omap-cpufreq.o ################################################################################## diff --git a/drivers/cpufreq/exynos-cpufreq.c b/drivers/cpufreq/exynos-cpufreq.c index 2c63b68d5b23..b243a7ee01f6 100644 --- a/drivers/cpufreq/exynos-cpufreq.c +++ b/drivers/cpufreq/exynos-cpufreq.c @@ -256,6 +256,8 @@ static int __init exynos_cpufreq_init(void) 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__); diff --git a/drivers/cpufreq/exynos5250-cpufreq.c b/drivers/cpufreq/exynos5250-cpufreq.c new file mode 100644 index 000000000000..a88331644ebf --- /dev/null +++ b/drivers/cpufreq/exynos5250-cpufreq.c @@ -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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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); -- cgit v1.2.3-59-g8ed1b