From 92e03c41a415e8e9e8009a1f5bbb9036f3bfb2f4 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Wed, 13 Jul 2011 17:58:32 -0400 Subject: [CPUFREQ] Handle CPUs with different capabilities in acpi-cpufreq acpi-cpufreq checks each CPU for aperf/mperf support, but only sets a global flag. This will cause errors if some CPUs in the system don't support the feature. Check boot_cpu_has() instead in order to make sure that all CPUs support it. Signed-off-by: Matthew Garrett Signed-off-by: Dave Jones diff --git a/drivers/cpufreq/acpi-cpufreq.c b/drivers/cpufreq/acpi-cpufreq.c index 596d5dd..56c6c6b 100644 --- a/drivers/cpufreq/acpi-cpufreq.c +++ b/drivers/cpufreq/acpi-cpufreq.c @@ -655,7 +655,7 @@ static int acpi_cpufreq_cpu_init(struct cpufreq_policy *policy) acpi_processor_notify_smm(THIS_MODULE); /* Check for APERF/MPERF support in hardware */ - if (cpu_has(c, X86_FEATURE_APERFMPERF)) + if (boot_cpu_has(X86_FEATURE_APERFMPERF)) acpi_cpufreq_driver.getavg = cpufreq_get_measured_perf; pr_debug("CPU%u - ACPI performance management activated.\n", cpu); -- cgit v0.10.2 From be2de99beaca6506a1f97a636750c108a41b5c00 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 10 May 2011 15:42:08 +0200 Subject: [CPUFREQ/S3C64xx] Move S3C64xx CPUfreq driver into drivers/cpufreq This is a straight code motion patch, there are no changes to the driver itself. The Kconfig is left untouched as the ARM CPUfreq Kconfig is all in one big block in arm/Kconfig and should be moved en masse rather than being done piecemeal. Signed-off-by: Mark Brown Signed-off-by: Dave Jones diff --git a/arch/arm/mach-s3c64xx/Makefile b/arch/arm/mach-s3c64xx/Makefile index 4657363..f5a7144 100644 --- a/arch/arm/mach-s3c64xx/Makefile +++ b/arch/arm/mach-s3c64xx/Makefile @@ -23,10 +23,6 @@ obj-$(CONFIG_CPU_S3C6410) += s3c6410.o obj-y += irq.o obj-y += irq-eint.o -# CPU frequency scaling - -obj-$(CONFIG_CPU_FREQ_S3C64XX) += cpufreq.o - # DMA support obj-$(CONFIG_S3C64XX_DMA) += dma.o diff --git a/arch/arm/mach-s3c64xx/cpufreq.c b/arch/arm/mach-s3c64xx/cpufreq.c deleted file mode 100644 index 4375b97..0000000 --- a/arch/arm/mach-s3c64xx/cpufreq.c +++ /dev/null @@ -1,270 +0,0 @@ -/* linux/arch/arm/plat-s3c64xx/cpufreq.c - * - * Copyright 2009 Wolfson Microelectronics plc - * - * S3C64xx CPUfreq 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 - -static struct clk *armclk; -static struct regulator *vddarm; -static unsigned long regulator_latency; - -#ifdef CONFIG_CPU_S3C6410 -struct s3c64xx_dvfs { - unsigned int vddarm_min; - unsigned int vddarm_max; -}; - -static struct s3c64xx_dvfs s3c64xx_dvfs_table[] = { - [0] = { 1000000, 1150000 }, - [1] = { 1050000, 1150000 }, - [2] = { 1100000, 1150000 }, - [3] = { 1200000, 1350000 }, -}; - -static struct cpufreq_frequency_table s3c64xx_freq_table[] = { - { 0, 66000 }, - { 0, 133000 }, - { 1, 222000 }, - { 1, 266000 }, - { 2, 333000 }, - { 2, 400000 }, - { 2, 532000 }, - { 2, 533000 }, - { 3, 667000 }, - { 0, CPUFREQ_TABLE_END }, -}; -#endif - -static int s3c64xx_cpufreq_verify_speed(struct cpufreq_policy *policy) -{ - if (policy->cpu != 0) - return -EINVAL; - - return cpufreq_frequency_table_verify(policy, s3c64xx_freq_table); -} - -static unsigned int s3c64xx_cpufreq_get_speed(unsigned int cpu) -{ - if (cpu != 0) - return 0; - - return clk_get_rate(armclk) / 1000; -} - -static int s3c64xx_cpufreq_set_target(struct cpufreq_policy *policy, - unsigned int target_freq, - unsigned int relation) -{ - int ret; - unsigned int i; - struct cpufreq_freqs freqs; - struct s3c64xx_dvfs *dvfs; - - ret = cpufreq_frequency_table_target(policy, s3c64xx_freq_table, - target_freq, relation, &i); - if (ret != 0) - return ret; - - freqs.cpu = 0; - freqs.old = clk_get_rate(armclk) / 1000; - freqs.new = s3c64xx_freq_table[i].frequency; - freqs.flags = 0; - dvfs = &s3c64xx_dvfs_table[s3c64xx_freq_table[i].index]; - - if (freqs.old == freqs.new) - return 0; - - pr_debug("cpufreq: Transition %d-%dkHz\n", freqs.old, freqs.new); - - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - -#ifdef CONFIG_REGULATOR - if (vddarm && freqs.new > freqs.old) { - ret = regulator_set_voltage(vddarm, - dvfs->vddarm_min, - dvfs->vddarm_max); - if (ret != 0) { - pr_err("cpufreq: Failed to set VDDARM for %dkHz: %d\n", - freqs.new, ret); - goto err; - } - } -#endif - - ret = clk_set_rate(armclk, freqs.new * 1000); - if (ret < 0) { - pr_err("cpufreq: Failed to set rate %dkHz: %d\n", - freqs.new, ret); - goto err; - } - -#ifdef CONFIG_REGULATOR - if (vddarm && freqs.new < freqs.old) { - ret = regulator_set_voltage(vddarm, - dvfs->vddarm_min, - dvfs->vddarm_max); - if (ret != 0) { - pr_err("cpufreq: Failed to set VDDARM for %dkHz: %d\n", - freqs.new, ret); - goto err_clk; - } - } -#endif - - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - - pr_debug("cpufreq: Set actual frequency %lukHz\n", - clk_get_rate(armclk) / 1000); - - return 0; - -err_clk: - if (clk_set_rate(armclk, freqs.old * 1000) < 0) - pr_err("Failed to restore original clock rate\n"); -err: - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - - return ret; -} - -#ifdef CONFIG_REGULATOR -static void __init s3c64xx_cpufreq_config_regulator(void) -{ - int count, v, i, found; - struct cpufreq_frequency_table *freq; - struct s3c64xx_dvfs *dvfs; - - count = regulator_count_voltages(vddarm); - if (count < 0) { - pr_err("cpufreq: Unable to check supported voltages\n"); - } - - freq = s3c64xx_freq_table; - while (count > 0 && freq->frequency != CPUFREQ_TABLE_END) { - if (freq->frequency == CPUFREQ_ENTRY_INVALID) - continue; - - dvfs = &s3c64xx_dvfs_table[freq->index]; - found = 0; - - for (i = 0; i < count; i++) { - v = regulator_list_voltage(vddarm, i); - if (v >= dvfs->vddarm_min && v <= dvfs->vddarm_max) - found = 1; - } - - if (!found) { - pr_debug("cpufreq: %dkHz unsupported by regulator\n", - freq->frequency); - freq->frequency = CPUFREQ_ENTRY_INVALID; - } - - freq++; - } - - /* Guess based on having to do an I2C/SPI write; in future we - * will be able to query the regulator performance here. */ - regulator_latency = 1 * 1000 * 1000; -} -#endif - -static int s3c64xx_cpufreq_driver_init(struct cpufreq_policy *policy) -{ - int ret; - struct cpufreq_frequency_table *freq; - - if (policy->cpu != 0) - return -EINVAL; - - if (s3c64xx_freq_table == NULL) { - pr_err("cpufreq: No frequency information for this CPU\n"); - return -ENODEV; - } - - armclk = clk_get(NULL, "armclk"); - if (IS_ERR(armclk)) { - pr_err("cpufreq: Unable to obtain ARMCLK: %ld\n", - PTR_ERR(armclk)); - return PTR_ERR(armclk); - } - -#ifdef CONFIG_REGULATOR - vddarm = regulator_get(NULL, "vddarm"); - if (IS_ERR(vddarm)) { - ret = PTR_ERR(vddarm); - pr_err("cpufreq: Failed to obtain VDDARM: %d\n", ret); - pr_err("cpufreq: Only frequency scaling available\n"); - vddarm = NULL; - } else { - s3c64xx_cpufreq_config_regulator(); - } -#endif - - freq = s3c64xx_freq_table; - while (freq->frequency != CPUFREQ_TABLE_END) { - unsigned long r; - - /* Check for frequencies we can generate */ - r = clk_round_rate(armclk, freq->frequency * 1000); - r /= 1000; - if (r != freq->frequency) { - pr_debug("cpufreq: %dkHz unsupported by clock\n", - freq->frequency); - freq->frequency = CPUFREQ_ENTRY_INVALID; - } - - /* If we have no regulator then assume startup - * frequency is the maximum we can support. */ - if (!vddarm && freq->frequency > s3c64xx_cpufreq_get_speed(0)) - freq->frequency = CPUFREQ_ENTRY_INVALID; - - freq++; - } - - policy->cur = clk_get_rate(armclk) / 1000; - - /* Datasheet says PLL stabalisation time (if we were to use - * the PLLs, which we don't currently) is ~300us worst case, - * but add some fudge. - */ - policy->cpuinfo.transition_latency = (500 * 1000) + regulator_latency; - - ret = cpufreq_frequency_table_cpuinfo(policy, s3c64xx_freq_table); - if (ret != 0) { - pr_err("cpufreq: Failed to configure frequency table: %d\n", - ret); - regulator_put(vddarm); - clk_put(armclk); - } - - return ret; -} - -static struct cpufreq_driver s3c64xx_cpufreq_driver = { - .owner = THIS_MODULE, - .flags = 0, - .verify = s3c64xx_cpufreq_verify_speed, - .target = s3c64xx_cpufreq_set_target, - .get = s3c64xx_cpufreq_get_speed, - .init = s3c64xx_cpufreq_driver_init, - .name = "s3c", -}; - -static int __init s3c64xx_cpufreq_init(void) -{ - return cpufreq_register_driver(&s3c64xx_cpufreq_driver); -} -module_init(s3c64xx_cpufreq_init); diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index e2fc2d2..0fd8cae 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -41,3 +41,4 @@ obj-$(CONFIG_X86_CPUFREQ_NFORCE2) += cpufreq-nforce2.o # ARM SoC drivers obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o +obj-$(CONFIG_CPU_FREQ_S3C64XX) += s3c64xx.o diff --git a/drivers/cpufreq/s3c64xx.c b/drivers/cpufreq/s3c64xx.c new file mode 100644 index 0000000..fc3f180 --- /dev/null +++ b/drivers/cpufreq/s3c64xx.c @@ -0,0 +1,269 @@ +/* + * Copyright 2009 Wolfson Microelectronics plc + * + * S3C64xx CPUfreq 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 + +static struct clk *armclk; +static struct regulator *vddarm; +static unsigned long regulator_latency; + +#ifdef CONFIG_CPU_S3C6410 +struct s3c64xx_dvfs { + unsigned int vddarm_min; + unsigned int vddarm_max; +}; + +static struct s3c64xx_dvfs s3c64xx_dvfs_table[] = { + [0] = { 1000000, 1150000 }, + [1] = { 1050000, 1150000 }, + [2] = { 1100000, 1150000 }, + [3] = { 1200000, 1350000 }, +}; + +static struct cpufreq_frequency_table s3c64xx_freq_table[] = { + { 0, 66000 }, + { 0, 133000 }, + { 1, 222000 }, + { 1, 266000 }, + { 2, 333000 }, + { 2, 400000 }, + { 2, 532000 }, + { 2, 533000 }, + { 3, 667000 }, + { 0, CPUFREQ_TABLE_END }, +}; +#endif + +static int s3c64xx_cpufreq_verify_speed(struct cpufreq_policy *policy) +{ + if (policy->cpu != 0) + return -EINVAL; + + return cpufreq_frequency_table_verify(policy, s3c64xx_freq_table); +} + +static unsigned int s3c64xx_cpufreq_get_speed(unsigned int cpu) +{ + if (cpu != 0) + return 0; + + return clk_get_rate(armclk) / 1000; +} + +static int s3c64xx_cpufreq_set_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + int ret; + unsigned int i; + struct cpufreq_freqs freqs; + struct s3c64xx_dvfs *dvfs; + + ret = cpufreq_frequency_table_target(policy, s3c64xx_freq_table, + target_freq, relation, &i); + if (ret != 0) + return ret; + + freqs.cpu = 0; + freqs.old = clk_get_rate(armclk) / 1000; + freqs.new = s3c64xx_freq_table[i].frequency; + freqs.flags = 0; + dvfs = &s3c64xx_dvfs_table[s3c64xx_freq_table[i].index]; + + if (freqs.old == freqs.new) + return 0; + + pr_debug("cpufreq: Transition %d-%dkHz\n", freqs.old, freqs.new); + + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + +#ifdef CONFIG_REGULATOR + if (vddarm && freqs.new > freqs.old) { + ret = regulator_set_voltage(vddarm, + dvfs->vddarm_min, + dvfs->vddarm_max); + if (ret != 0) { + pr_err("cpufreq: Failed to set VDDARM for %dkHz: %d\n", + freqs.new, ret); + goto err; + } + } +#endif + + ret = clk_set_rate(armclk, freqs.new * 1000); + if (ret < 0) { + pr_err("cpufreq: Failed to set rate %dkHz: %d\n", + freqs.new, ret); + goto err; + } + +#ifdef CONFIG_REGULATOR + if (vddarm && freqs.new < freqs.old) { + ret = regulator_set_voltage(vddarm, + dvfs->vddarm_min, + dvfs->vddarm_max); + if (ret != 0) { + pr_err("cpufreq: Failed to set VDDARM for %dkHz: %d\n", + freqs.new, ret); + goto err_clk; + } + } +#endif + + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + pr_debug("cpufreq: Set actual frequency %lukHz\n", + clk_get_rate(armclk) / 1000); + + return 0; + +err_clk: + if (clk_set_rate(armclk, freqs.old * 1000) < 0) + pr_err("Failed to restore original clock rate\n"); +err: + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + return ret; +} + +#ifdef CONFIG_REGULATOR +static void __init s3c64xx_cpufreq_config_regulator(void) +{ + int count, v, i, found; + struct cpufreq_frequency_table *freq; + struct s3c64xx_dvfs *dvfs; + + count = regulator_count_voltages(vddarm); + if (count < 0) { + pr_err("cpufreq: Unable to check supported voltages\n"); + } + + freq = s3c64xx_freq_table; + while (count > 0 && freq->frequency != CPUFREQ_TABLE_END) { + if (freq->frequency == CPUFREQ_ENTRY_INVALID) + continue; + + dvfs = &s3c64xx_dvfs_table[freq->index]; + found = 0; + + for (i = 0; i < count; i++) { + v = regulator_list_voltage(vddarm, i); + if (v >= dvfs->vddarm_min && v <= dvfs->vddarm_max) + found = 1; + } + + if (!found) { + pr_debug("cpufreq: %dkHz unsupported by regulator\n", + freq->frequency); + freq->frequency = CPUFREQ_ENTRY_INVALID; + } + + freq++; + } + + /* Guess based on having to do an I2C/SPI write; in future we + * will be able to query the regulator performance here. */ + regulator_latency = 1 * 1000 * 1000; +} +#endif + +static int s3c64xx_cpufreq_driver_init(struct cpufreq_policy *policy) +{ + int ret; + struct cpufreq_frequency_table *freq; + + if (policy->cpu != 0) + return -EINVAL; + + if (s3c64xx_freq_table == NULL) { + pr_err("cpufreq: No frequency information for this CPU\n"); + return -ENODEV; + } + + armclk = clk_get(NULL, "armclk"); + if (IS_ERR(armclk)) { + pr_err("cpufreq: Unable to obtain ARMCLK: %ld\n", + PTR_ERR(armclk)); + return PTR_ERR(armclk); + } + +#ifdef CONFIG_REGULATOR + vddarm = regulator_get(NULL, "vddarm"); + if (IS_ERR(vddarm)) { + ret = PTR_ERR(vddarm); + pr_err("cpufreq: Failed to obtain VDDARM: %d\n", ret); + pr_err("cpufreq: Only frequency scaling available\n"); + vddarm = NULL; + } else { + s3c64xx_cpufreq_config_regulator(); + } +#endif + + freq = s3c64xx_freq_table; + while (freq->frequency != CPUFREQ_TABLE_END) { + unsigned long r; + + /* Check for frequencies we can generate */ + r = clk_round_rate(armclk, freq->frequency * 1000); + r /= 1000; + if (r != freq->frequency) { + pr_debug("cpufreq: %dkHz unsupported by clock\n", + freq->frequency); + freq->frequency = CPUFREQ_ENTRY_INVALID; + } + + /* If we have no regulator then assume startup + * frequency is the maximum we can support. */ + if (!vddarm && freq->frequency > s3c64xx_cpufreq_get_speed(0)) + freq->frequency = CPUFREQ_ENTRY_INVALID; + + freq++; + } + + policy->cur = clk_get_rate(armclk) / 1000; + + /* Datasheet says PLL stabalisation time (if we were to use + * the PLLs, which we don't currently) is ~300us worst case, + * but add some fudge. + */ + policy->cpuinfo.transition_latency = (500 * 1000) + regulator_latency; + + ret = cpufreq_frequency_table_cpuinfo(policy, s3c64xx_freq_table); + if (ret != 0) { + pr_err("cpufreq: Failed to configure frequency table: %d\n", + ret); + regulator_put(vddarm); + clk_put(armclk); + } + + return ret; +} + +static struct cpufreq_driver s3c64xx_cpufreq_driver = { + .owner = THIS_MODULE, + .flags = 0, + .verify = s3c64xx_cpufreq_verify_speed, + .target = s3c64xx_cpufreq_set_target, + .get = s3c64xx_cpufreq_get_speed, + .init = s3c64xx_cpufreq_driver_init, + .name = "s3c", +}; + +static int __init s3c64xx_cpufreq_init(void) +{ + return cpufreq_register_driver(&s3c64xx_cpufreq_driver); +} +module_init(s3c64xx_cpufreq_init); -- cgit v0.10.2 From f7d770790f29781116d0de1339214934b8020c1e Mon Sep 17 00:00:00 2001 From: Kukjin Kim Date: Wed, 1 Jun 2011 14:18:22 -0700 Subject: [CPUFREQ] Move ARM Samsung cpufreq drivers to drivers/cpufreq/ According to discussion of the ARM arch subsystem migration, ARM cpufreq drivers move to drivers/cpufreq. So this patch adds Kconfig.arm for ARM like x86 and adds Samsung S5PV210 and EXYNOS4210 cpufreq driver compile in there. As a note, otherw will be moved. Cc: Dave Jones Signed-off-by: Kukjin Kim Signed-off-by: Dave Jones diff --git a/arch/arm/mach-exynos4/Makefile b/arch/arm/mach-exynos4/Makefile index 60fe5ec..1366995 100644 --- a/arch/arm/mach-exynos4/Makefile +++ b/arch/arm/mach-exynos4/Makefile @@ -15,7 +15,6 @@ obj- := obj-$(CONFIG_CPU_EXYNOS4210) += cpu.o init.o clock.o irq-combiner.o obj-$(CONFIG_CPU_EXYNOS4210) += setup-i2c0.o irq-eint.o dma.o obj-$(CONFIG_PM) += pm.o sleep.o -obj-$(CONFIG_CPU_FREQ) += cpufreq.o obj-$(CONFIG_CPU_IDLE) += cpuidle.o obj-$(CONFIG_SMP) += platsmp.o headsmp.o diff --git a/arch/arm/mach-exynos4/cpufreq.c b/arch/arm/mach-exynos4/cpufreq.c deleted file mode 100644 index a1bd258..0000000 --- a/arch/arm/mach-exynos4/cpufreq.c +++ /dev/null @@ -1,569 +0,0 @@ -/* linux/arch/arm/mach-exynos4/cpufreq.c - * - * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. - * http://www.samsung.com - * - * EXYNOS4 - 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 -#include - -#include -#include - -static struct clk *cpu_clk; -static struct clk *moutcore; -static struct clk *mout_mpll; -static struct clk *mout_apll; - -static struct regulator *arm_regulator; -static struct regulator *int_regulator; - -static struct cpufreq_freqs freqs; -static unsigned int memtype; - -enum exynos4_memory_type { - DDR2 = 4, - LPDDR2, - DDR3, -}; - -enum cpufreq_level_index { - L0, L1, L2, L3, CPUFREQ_LEVEL_END, -}; - -static struct cpufreq_frequency_table exynos4_freq_table[] = { - {L0, 1000*1000}, - {L1, 800*1000}, - {L2, 400*1000}, - {L3, 100*1000}, - {0, CPUFREQ_TABLE_END}, -}; - -static unsigned int clkdiv_cpu0[CPUFREQ_LEVEL_END][7] = { - /* - * Clock divider value for following - * { DIVCORE, DIVCOREM0, DIVCOREM1, DIVPERIPH, - * DIVATB, DIVPCLK_DBG, DIVAPLL } - */ - - /* ARM L0: 1000MHz */ - { 0, 3, 7, 3, 3, 0, 1 }, - - /* ARM L1: 800MHz */ - { 0, 3, 7, 3, 3, 0, 1 }, - - /* ARM L2: 400MHz */ - { 0, 1, 3, 1, 3, 0, 1 }, - - /* ARM L3: 100MHz */ - { 0, 0, 1, 0, 3, 1, 1 }, -}; - -static unsigned int clkdiv_cpu1[CPUFREQ_LEVEL_END][2] = { - /* - * Clock divider value for following - * { DIVCOPY, DIVHPM } - */ - - /* ARM L0: 1000MHz */ - { 3, 0 }, - - /* ARM L1: 800MHz */ - { 3, 0 }, - - /* ARM L2: 400MHz */ - { 3, 0 }, - - /* ARM L3: 100MHz */ - { 3, 0 }, -}; - -static unsigned int clkdiv_dmc0[CPUFREQ_LEVEL_END][8] = { - /* - * Clock divider value for following - * { DIVACP, DIVACP_PCLK, DIVDPHY, DIVDMC, DIVDMCD - * DIVDMCP, DIVCOPY2, DIVCORE_TIMERS } - */ - - /* DMC L0: 400MHz */ - { 3, 1, 1, 1, 1, 1, 3, 1 }, - - /* DMC L1: 400MHz */ - { 3, 1, 1, 1, 1, 1, 3, 1 }, - - /* DMC L2: 266.7MHz */ - { 7, 1, 1, 2, 1, 1, 3, 1 }, - - /* DMC L3: 200MHz */ - { 7, 1, 1, 3, 1, 1, 3, 1 }, -}; - -static unsigned int clkdiv_top[CPUFREQ_LEVEL_END][5] = { - /* - * Clock divider value for following - * { DIVACLK200, DIVACLK100, DIVACLK160, DIVACLK133, DIVONENAND } - */ - - /* ACLK200 L0: 200MHz */ - { 3, 7, 4, 5, 1 }, - - /* ACLK200 L1: 200MHz */ - { 3, 7, 4, 5, 1 }, - - /* ACLK200 L2: 160MHz */ - { 4, 7, 5, 7, 1 }, - - /* ACLK200 L3: 133.3MHz */ - { 5, 7, 7, 7, 1 }, -}; - -static unsigned int clkdiv_lr_bus[CPUFREQ_LEVEL_END][2] = { - /* - * Clock divider value for following - * { DIVGDL/R, DIVGPL/R } - */ - - /* ACLK_GDL/R L0: 200MHz */ - { 3, 1 }, - - /* ACLK_GDL/R L1: 200MHz */ - { 3, 1 }, - - /* ACLK_GDL/R L2: 160MHz */ - { 4, 1 }, - - /* ACLK_GDL/R L3: 133.3MHz */ - { 5, 1 }, -}; - -struct cpufreq_voltage_table { - unsigned int index; /* any */ - unsigned int arm_volt; /* uV */ - unsigned int int_volt; -}; - -static struct cpufreq_voltage_table exynos4_volt_table[CPUFREQ_LEVEL_END] = { - { - .index = L0, - .arm_volt = 1200000, - .int_volt = 1100000, - }, { - .index = L1, - .arm_volt = 1100000, - .int_volt = 1100000, - }, { - .index = L2, - .arm_volt = 1000000, - .int_volt = 1000000, - }, { - .index = L3, - .arm_volt = 900000, - .int_volt = 1000000, - }, -}; - -static unsigned int exynos4_apll_pms_table[CPUFREQ_LEVEL_END] = { - /* APLL FOUT L0: 1000MHz */ - ((250 << 16) | (6 << 8) | 1), - - /* APLL FOUT L1: 800MHz */ - ((200 << 16) | (6 << 8) | 1), - - /* APLL FOUT L2 : 400MHz */ - ((200 << 16) | (6 << 8) | 2), - - /* APLL FOUT L3: 100MHz */ - ((200 << 16) | (6 << 8) | 4), -}; - -int exynos4_verify_speed(struct cpufreq_policy *policy) -{ - return cpufreq_frequency_table_verify(policy, exynos4_freq_table); -} - -unsigned int exynos4_getspeed(unsigned int cpu) -{ - return clk_get_rate(cpu_clk) / 1000; -} - -void exynos4_set_clkdiv(unsigned int div_index) -{ - unsigned int tmp; - - /* Change Divider - CPU0 */ - - tmp = __raw_readl(S5P_CLKDIV_CPU); - - 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 |= ((clkdiv_cpu0[div_index][0] << S5P_CLKDIV_CPU0_CORE_SHIFT) | - (clkdiv_cpu0[div_index][1] << S5P_CLKDIV_CPU0_COREM0_SHIFT) | - (clkdiv_cpu0[div_index][2] << S5P_CLKDIV_CPU0_COREM1_SHIFT) | - (clkdiv_cpu0[div_index][3] << S5P_CLKDIV_CPU0_PERIPH_SHIFT) | - (clkdiv_cpu0[div_index][4] << S5P_CLKDIV_CPU0_ATB_SHIFT) | - (clkdiv_cpu0[div_index][5] << S5P_CLKDIV_CPU0_PCLKDBG_SHIFT) | - (clkdiv_cpu0[div_index][6] << S5P_CLKDIV_CPU0_APLL_SHIFT)); - - __raw_writel(tmp, S5P_CLKDIV_CPU); - - do { - tmp = __raw_readl(S5P_CLKDIV_STATCPU); - } while (tmp & 0x1111111); - - /* Change Divider - CPU1 */ - - tmp = __raw_readl(S5P_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); - - do { - tmp = __raw_readl(S5P_CLKDIV_STATCPU1); - } while (tmp & 0x11); - - /* Change Divider - DMC0 */ - - tmp = __raw_readl(S5P_CLKDIV_DMC0); - - tmp &= ~(S5P_CLKDIV_DMC0_ACP_MASK | S5P_CLKDIV_DMC0_ACPPCLK_MASK | - S5P_CLKDIV_DMC0_DPHY_MASK | S5P_CLKDIV_DMC0_DMC_MASK | - S5P_CLKDIV_DMC0_DMCD_MASK | S5P_CLKDIV_DMC0_DMCP_MASK | - S5P_CLKDIV_DMC0_COPY2_MASK | S5P_CLKDIV_DMC0_CORETI_MASK); - - tmp |= ((clkdiv_dmc0[div_index][0] << S5P_CLKDIV_DMC0_ACP_SHIFT) | - (clkdiv_dmc0[div_index][1] << S5P_CLKDIV_DMC0_ACPPCLK_SHIFT) | - (clkdiv_dmc0[div_index][2] << S5P_CLKDIV_DMC0_DPHY_SHIFT) | - (clkdiv_dmc0[div_index][3] << S5P_CLKDIV_DMC0_DMC_SHIFT) | - (clkdiv_dmc0[div_index][4] << S5P_CLKDIV_DMC0_DMCD_SHIFT) | - (clkdiv_dmc0[div_index][5] << S5P_CLKDIV_DMC0_DMCP_SHIFT) | - (clkdiv_dmc0[div_index][6] << S5P_CLKDIV_DMC0_COPY2_SHIFT) | - (clkdiv_dmc0[div_index][7] << S5P_CLKDIV_DMC0_CORETI_SHIFT)); - - __raw_writel(tmp, S5P_CLKDIV_DMC0); - - do { - tmp = __raw_readl(S5P_CLKDIV_STAT_DMC0); - } while (tmp & 0x11111111); - - /* Change Divider - TOP */ - - tmp = __raw_readl(S5P_CLKDIV_TOP); - - tmp &= ~(S5P_CLKDIV_TOP_ACLK200_MASK | S5P_CLKDIV_TOP_ACLK100_MASK | - S5P_CLKDIV_TOP_ACLK160_MASK | S5P_CLKDIV_TOP_ACLK133_MASK | - S5P_CLKDIV_TOP_ONENAND_MASK); - - tmp |= ((clkdiv_top[div_index][0] << S5P_CLKDIV_TOP_ACLK200_SHIFT) | - (clkdiv_top[div_index][1] << S5P_CLKDIV_TOP_ACLK100_SHIFT) | - (clkdiv_top[div_index][2] << S5P_CLKDIV_TOP_ACLK160_SHIFT) | - (clkdiv_top[div_index][3] << S5P_CLKDIV_TOP_ACLK133_SHIFT) | - (clkdiv_top[div_index][4] << S5P_CLKDIV_TOP_ONENAND_SHIFT)); - - __raw_writel(tmp, S5P_CLKDIV_TOP); - - do { - tmp = __raw_readl(S5P_CLKDIV_STAT_TOP); - } while (tmp & 0x11111); - - /* Change Divider - LEFTBUS */ - - tmp = __raw_readl(S5P_CLKDIV_LEFTBUS); - - tmp &= ~(S5P_CLKDIV_BUS_GDLR_MASK | S5P_CLKDIV_BUS_GPLR_MASK); - - tmp |= ((clkdiv_lr_bus[div_index][0] << S5P_CLKDIV_BUS_GDLR_SHIFT) | - (clkdiv_lr_bus[div_index][1] << S5P_CLKDIV_BUS_GPLR_SHIFT)); - - __raw_writel(tmp, S5P_CLKDIV_LEFTBUS); - - do { - tmp = __raw_readl(S5P_CLKDIV_STAT_LEFTBUS); - } while (tmp & 0x11); - - /* Change Divider - RIGHTBUS */ - - tmp = __raw_readl(S5P_CLKDIV_RIGHTBUS); - - tmp &= ~(S5P_CLKDIV_BUS_GDLR_MASK | S5P_CLKDIV_BUS_GPLR_MASK); - - tmp |= ((clkdiv_lr_bus[div_index][0] << S5P_CLKDIV_BUS_GDLR_SHIFT) | - (clkdiv_lr_bus[div_index][1] << S5P_CLKDIV_BUS_GPLR_SHIFT)); - - __raw_writel(tmp, S5P_CLKDIV_RIGHTBUS); - - do { - tmp = __raw_readl(S5P_CLKDIV_STAT_RIGHTBUS); - } while (tmp & 0x11); -} - -static void exynos4_set_apll(unsigned int index) -{ - unsigned int tmp; - - /* 1. MUX_CORE_SEL = MPLL, ARMCLK uses MPLL for lock time */ - clk_set_parent(moutcore, mout_mpll); - - do { - tmp = (__raw_readl(S5P_CLKMUX_STATCPU) - >> S5P_CLKSRC_CPU_MUXCORE_SHIFT); - tmp &= 0x7; - } while (tmp != 0x2); - - /* 2. Set APLL Lock time */ - __raw_writel(S5P_APLL_LOCKTIME, S5P_APLL_LOCK); - - /* 3. Change PLL PMS values */ - tmp = __raw_readl(S5P_APLL_CON0); - tmp &= ~((0x3ff << 16) | (0x3f << 8) | (0x7 << 0)); - tmp |= exynos4_apll_pms_table[index]; - __raw_writel(tmp, S5P_APLL_CON0); - - /* 4. wait_lock_time */ - do { - tmp = __raw_readl(S5P_APLL_CON0); - } while (!(tmp & (0x1 << S5P_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)); -} - -static void exynos4_set_frequency(unsigned int old_index, unsigned int new_index) -{ - unsigned int tmp; - - if (old_index > new_index) { - /* The frequency changing to L0 needs to change apll */ - if (freqs.new == exynos4_freq_table[L0].frequency) { - /* 1. Change the system clock divider values */ - exynos4_set_clkdiv(new_index); - - /* 2. Change the apll m,p,s value */ - exynos4_set_apll(new_index); - } else { - /* 1. Change the system clock divider values */ - exynos4_set_clkdiv(new_index); - - /* 2. Change just s value in apll m,p,s value */ - tmp = __raw_readl(S5P_APLL_CON0); - tmp &= ~(0x7 << 0); - tmp |= (exynos4_apll_pms_table[new_index] & 0x7); - __raw_writel(tmp, S5P_APLL_CON0); - } - } - - else if (old_index < new_index) { - /* The frequency changing from L0 needs to change apll */ - if (freqs.old == exynos4_freq_table[L0].frequency) { - /* 1. Change the apll m,p,s value */ - exynos4_set_apll(new_index); - - /* 2. Change the system clock divider values */ - exynos4_set_clkdiv(new_index); - } else { - /* 1. Change just s value in apll m,p,s value */ - tmp = __raw_readl(S5P_APLL_CON0); - tmp &= ~(0x7 << 0); - tmp |= (exynos4_apll_pms_table[new_index] & 0x7); - __raw_writel(tmp, S5P_APLL_CON0); - - /* 2. Change the system clock divider values */ - exynos4_set_clkdiv(new_index); - } - } -} - -static int exynos4_target(struct cpufreq_policy *policy, - unsigned int target_freq, - unsigned int relation) -{ - unsigned int index, old_index; - unsigned int arm_volt, int_volt; - - freqs.old = exynos4_getspeed(policy->cpu); - - if (cpufreq_frequency_table_target(policy, exynos4_freq_table, - freqs.old, relation, &old_index)) - return -EINVAL; - - if (cpufreq_frequency_table_target(policy, exynos4_freq_table, - target_freq, relation, &index)) - return -EINVAL; - - freqs.new = exynos4_freq_table[index].frequency; - freqs.cpu = policy->cpu; - - if (freqs.new == freqs.old) - return 0; - - /* get the voltage value */ - arm_volt = exynos4_volt_table[index].arm_volt; - int_volt = exynos4_volt_table[index].int_volt; - - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - - /* control regulator */ - if (freqs.new > freqs.old) { - /* Voltage up */ - regulator_set_voltage(arm_regulator, arm_volt, arm_volt); - regulator_set_voltage(int_regulator, int_volt, int_volt); - } - - /* Clock Configuration Procedure */ - exynos4_set_frequency(old_index, index); - - /* control regulator */ - if (freqs.new < freqs.old) { - /* Voltage down */ - regulator_set_voltage(arm_regulator, arm_volt, arm_volt); - regulator_set_voltage(int_regulator, int_volt, int_volt); - } - - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - - return 0; -} - -#ifdef CONFIG_PM -static int exynos4_cpufreq_suspend(struct cpufreq_policy *policy) -{ - return 0; -} - -static int exynos4_cpufreq_resume(struct cpufreq_policy *policy) -{ - return 0; -} -#endif - -static int exynos4_cpufreq_cpu_init(struct cpufreq_policy *policy) -{ - policy->cur = policy->min = policy->max = exynos4_getspeed(policy->cpu); - - cpufreq_frequency_table_get_attr(exynos4_freq_table, policy->cpu); - - /* set the transition latency value */ - policy->cpuinfo.transition_latency = 100000; - - /* - * EXYNOS4 multi-core processors has 2 cores - * that the frequency cannot be set independently. - * Each cpu is bound to the same speed. - * So the affected cpu is all of the cpus. - */ - cpumask_setall(policy->cpus); - - return cpufreq_frequency_table_cpuinfo(policy, exynos4_freq_table); -} - -static struct cpufreq_driver exynos4_driver = { - .flags = CPUFREQ_STICKY, - .verify = exynos4_verify_speed, - .target = exynos4_target, - .get = exynos4_getspeed, - .init = exynos4_cpufreq_cpu_init, - .name = "exynos4_cpufreq", -#ifdef CONFIG_PM - .suspend = exynos4_cpufreq_suspend, - .resume = exynos4_cpufreq_resume, -#endif -}; - -static int __init exynos4_cpufreq_init(void) -{ - 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 out; - - mout_mpll = clk_get(NULL, "mout_mpll"); - if (IS_ERR(mout_mpll)) - goto out; - - mout_apll = clk_get(NULL, "mout_apll"); - if (IS_ERR(mout_apll)) - goto out; - - arm_regulator = regulator_get(NULL, "vdd_arm"); - if (IS_ERR(arm_regulator)) { - printk(KERN_ERR "failed to get resource %s\n", "vdd_arm"); - goto out; - } - - int_regulator = regulator_get(NULL, "vdd_int"); - if (IS_ERR(int_regulator)) { - printk(KERN_ERR "failed to get resource %s\n", "vdd_int"); - goto out; - } - - /* - * Check DRAM type. - * Because DVFS level is different according to DRAM type. - */ - memtype = __raw_readl(S5P_VA_DMC0 + S5P_DMC0_MEMCON_OFFSET); - memtype = (memtype >> S5P_DMC0_MEMTYPE_SHIFT); - memtype &= S5P_DMC0_MEMTYPE_MASK; - - if ((memtype < DDR2) && (memtype > DDR3)) { - printk(KERN_ERR "%s: wrong memtype= 0x%x\n", __func__, memtype); - goto out; - } else { - printk(KERN_DEBUG "%s: memtype= 0x%x\n", __func__, memtype); - } - - return cpufreq_register_driver(&exynos4_driver); - -out: - if (!IS_ERR(cpu_clk)) - clk_put(cpu_clk); - - if (!IS_ERR(moutcore)) - clk_put(moutcore); - - if (!IS_ERR(mout_mpll)) - clk_put(mout_mpll); - - if (!IS_ERR(mout_apll)) - clk_put(mout_apll); - - if (!IS_ERR(arm_regulator)) - regulator_put(arm_regulator); - - if (!IS_ERR(int_regulator)) - regulator_put(int_regulator); - - printk(KERN_ERR "%s: failed initialization\n", __func__); - - return -EINVAL; -} -late_initcall(exynos4_cpufreq_init); diff --git a/arch/arm/mach-s5pv210/Makefile b/arch/arm/mach-s5pv210/Makefile index 50907ac..599a3c0 100644 --- a/arch/arm/mach-s5pv210/Makefile +++ b/arch/arm/mach-s5pv210/Makefile @@ -15,7 +15,6 @@ obj- := obj-$(CONFIG_CPU_S5PV210) += cpu.o init.o clock.o dma.o obj-$(CONFIG_CPU_S5PV210) += setup-i2c0.o obj-$(CONFIG_S5PV210_PM) += pm.o sleep.o -obj-$(CONFIG_CPU_FREQ) += cpufreq.o # machine support diff --git a/arch/arm/mach-s5pv210/cpufreq.c b/arch/arm/mach-s5pv210/cpufreq.c deleted file mode 100644 index 153af8b..0000000 --- a/arch/arm/mach-s5pv210/cpufreq.c +++ /dev/null @@ -1,485 +0,0 @@ -/* linux/arch/arm/mach-s5pv210/cpufreq.c - * - * Copyright (c) 2010 Samsung Electronics Co., Ltd. - * http://www.samsung.com - * - * CPU frequency scaling for S5PC110/S5PV210 - * - * 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 - -static struct clk *cpu_clk; -static struct clk *dmc0_clk; -static struct clk *dmc1_clk; -static struct cpufreq_freqs freqs; - -/* APLL M,P,S values for 1G/800Mhz */ -#define APLL_VAL_1000 ((1 << 31) | (125 << 16) | (3 << 8) | 1) -#define APLL_VAL_800 ((1 << 31) | (100 << 16) | (3 << 8) | 1) - -/* - * DRAM configurations to calculate refresh counter for changing - * frequency of memory. - */ -struct dram_conf { - unsigned long freq; /* HZ */ - unsigned long refresh; /* DRAM refresh counter * 1000 */ -}; - -/* DRAM configuration (DMC0 and DMC1) */ -static struct dram_conf s5pv210_dram_conf[2]; - -enum perf_level { - L0, L1, L2, L3, L4, -}; - -enum s5pv210_mem_type { - LPDDR = 0x1, - LPDDR2 = 0x2, - DDR2 = 0x4, -}; - -enum s5pv210_dmc_port { - DMC0 = 0, - DMC1, -}; - -static struct cpufreq_frequency_table s5pv210_freq_table[] = { - {L0, 1000*1000}, - {L1, 800*1000}, - {L2, 400*1000}, - {L3, 200*1000}, - {L4, 100*1000}, - {0, CPUFREQ_TABLE_END}, -}; - -static u32 clkdiv_val[5][11] = { - /* - * Clock divider value for following - * { APLL, A2M, HCLK_MSYS, PCLK_MSYS, - * HCLK_DSYS, PCLK_DSYS, HCLK_PSYS, PCLK_PSYS, - * ONEDRAM, MFC, G3D } - */ - - /* L0 : [1000/200/100][166/83][133/66][200/200] */ - {0, 4, 4, 1, 3, 1, 4, 1, 3, 0, 0}, - - /* L1 : [800/200/100][166/83][133/66][200/200] */ - {0, 3, 3, 1, 3, 1, 4, 1, 3, 0, 0}, - - /* L2 : [400/200/100][166/83][133/66][200/200] */ - {1, 3, 1, 1, 3, 1, 4, 1, 3, 0, 0}, - - /* L3 : [200/200/100][166/83][133/66][200/200] */ - {3, 3, 1, 1, 3, 1, 4, 1, 3, 0, 0}, - - /* L4 : [100/100/100][83/83][66/66][100/100] */ - {7, 7, 0, 0, 7, 0, 9, 0, 7, 0, 0}, -}; - -/* - * This function set DRAM refresh counter - * accoriding to operating frequency of DRAM - * ch: DMC port number 0 or 1 - * freq: Operating frequency of DRAM(KHz) - */ -static void s5pv210_set_refresh(enum s5pv210_dmc_port ch, unsigned long freq) -{ - unsigned long tmp, tmp1; - void __iomem *reg = NULL; - - if (ch == DMC0) { - reg = (S5P_VA_DMC0 + 0x30); - } else if (ch == DMC1) { - reg = (S5P_VA_DMC1 + 0x30); - } else { - printk(KERN_ERR "Cannot find DMC port\n"); - return; - } - - /* Find current DRAM frequency */ - tmp = s5pv210_dram_conf[ch].freq; - - do_div(tmp, freq); - - tmp1 = s5pv210_dram_conf[ch].refresh; - - do_div(tmp1, tmp); - - __raw_writel(tmp1, reg); -} - -int s5pv210_verify_speed(struct cpufreq_policy *policy) -{ - if (policy->cpu) - return -EINVAL; - - return cpufreq_frequency_table_verify(policy, s5pv210_freq_table); -} - -unsigned int s5pv210_getspeed(unsigned int cpu) -{ - if (cpu) - return 0; - - return clk_get_rate(cpu_clk) / 1000; -} - -static int s5pv210_target(struct cpufreq_policy *policy, - unsigned int target_freq, - unsigned int relation) -{ - unsigned long reg; - unsigned int index, priv_index; - unsigned int pll_changing = 0; - unsigned int bus_speed_changing = 0; - - freqs.old = s5pv210_getspeed(0); - - if (cpufreq_frequency_table_target(policy, s5pv210_freq_table, - target_freq, relation, &index)) - return -EINVAL; - - freqs.new = s5pv210_freq_table[index].frequency; - freqs.cpu = 0; - - if (freqs.new == freqs.old) - return 0; - - /* Finding current running level index */ - if (cpufreq_frequency_table_target(policy, s5pv210_freq_table, - freqs.old, relation, &priv_index)) - return -EINVAL; - - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - - if (freqs.new > freqs.old) { - /* Voltage up: will be implemented */ - } - - /* Check if there need to change PLL */ - if ((index == L0) || (priv_index == L0)) - pll_changing = 1; - - /* Check if there need to change System bus clock */ - if ((index == L4) || (priv_index == L4)) - bus_speed_changing = 1; - - if (bus_speed_changing) { - /* - * Reconfigure DRAM refresh counter value for minimum - * temporary clock while changing divider. - * expected clock is 83Mhz : 7.8usec/(1/83Mhz) = 0x287 - */ - if (pll_changing) - s5pv210_set_refresh(DMC1, 83000); - else - s5pv210_set_refresh(DMC1, 100000); - - s5pv210_set_refresh(DMC0, 83000); - } - - /* - * APLL should be changed in this level - * APLL -> MPLL(for stable transition) -> APLL - * Some clock source's clock API are not prepared. - * Do not use clock API in below code. - */ - if (pll_changing) { - /* - * 1. Temporary Change divider for MFC and G3D - * SCLKA2M(200/1=200)->(200/4=50)Mhz - */ - reg = __raw_readl(S5P_CLK_DIV2); - reg &= ~(S5P_CLKDIV2_G3D_MASK | S5P_CLKDIV2_MFC_MASK); - reg |= (3 << S5P_CLKDIV2_G3D_SHIFT) | - (3 << S5P_CLKDIV2_MFC_SHIFT); - __raw_writel(reg, S5P_CLK_DIV2); - - /* For MFC, G3D dividing */ - do { - reg = __raw_readl(S5P_CLKDIV_STAT0); - } while (reg & ((1 << 16) | (1 << 17))); - - /* - * 2. Change SCLKA2M(200Mhz)to SCLKMPLL in MFC_MUX, G3D MUX - * (200/4=50)->(667/4=166)Mhz - */ - reg = __raw_readl(S5P_CLK_SRC2); - reg &= ~(S5P_CLKSRC2_G3D_MASK | S5P_CLKSRC2_MFC_MASK); - reg |= (1 << S5P_CLKSRC2_G3D_SHIFT) | - (1 << S5P_CLKSRC2_MFC_SHIFT); - __raw_writel(reg, S5P_CLK_SRC2); - - do { - reg = __raw_readl(S5P_CLKMUX_STAT1); - } while (reg & ((1 << 7) | (1 << 3))); - - /* - * 3. DMC1 refresh count for 133Mhz if (index == L4) is - * true refresh counter is already programed in upper - * code. 0x287@83Mhz - */ - if (!bus_speed_changing) - s5pv210_set_refresh(DMC1, 133000); - - /* 4. SCLKAPLL -> SCLKMPLL */ - reg = __raw_readl(S5P_CLK_SRC0); - reg &= ~(S5P_CLKSRC0_MUX200_MASK); - reg |= (0x1 << S5P_CLKSRC0_MUX200_SHIFT); - __raw_writel(reg, S5P_CLK_SRC0); - - do { - reg = __raw_readl(S5P_CLKMUX_STAT0); - } while (reg & (0x1 << 18)); - - } - - /* Change divider */ - reg = __raw_readl(S5P_CLK_DIV0); - - reg &= ~(S5P_CLKDIV0_APLL_MASK | S5P_CLKDIV0_A2M_MASK | - S5P_CLKDIV0_HCLK200_MASK | S5P_CLKDIV0_PCLK100_MASK | - S5P_CLKDIV0_HCLK166_MASK | S5P_CLKDIV0_PCLK83_MASK | - S5P_CLKDIV0_HCLK133_MASK | S5P_CLKDIV0_PCLK66_MASK); - - reg |= ((clkdiv_val[index][0] << S5P_CLKDIV0_APLL_SHIFT) | - (clkdiv_val[index][1] << S5P_CLKDIV0_A2M_SHIFT) | - (clkdiv_val[index][2] << S5P_CLKDIV0_HCLK200_SHIFT) | - (clkdiv_val[index][3] << S5P_CLKDIV0_PCLK100_SHIFT) | - (clkdiv_val[index][4] << S5P_CLKDIV0_HCLK166_SHIFT) | - (clkdiv_val[index][5] << S5P_CLKDIV0_PCLK83_SHIFT) | - (clkdiv_val[index][6] << S5P_CLKDIV0_HCLK133_SHIFT) | - (clkdiv_val[index][7] << S5P_CLKDIV0_PCLK66_SHIFT)); - - __raw_writel(reg, S5P_CLK_DIV0); - - do { - reg = __raw_readl(S5P_CLKDIV_STAT0); - } while (reg & 0xff); - - /* ARM MCS value changed */ - reg = __raw_readl(S5P_ARM_MCS_CON); - reg &= ~0x3; - if (index >= L3) - reg |= 0x3; - else - reg |= 0x1; - - __raw_writel(reg, S5P_ARM_MCS_CON); - - if (pll_changing) { - /* 5. Set Lock time = 30us*24Mhz = 0x2cf */ - __raw_writel(0x2cf, S5P_APLL_LOCK); - - /* - * 6. Turn on APLL - * 6-1. Set PMS values - * 6-2. Wait untile the PLL is locked - */ - if (index == L0) - __raw_writel(APLL_VAL_1000, S5P_APLL_CON); - else - __raw_writel(APLL_VAL_800, S5P_APLL_CON); - - do { - reg = __raw_readl(S5P_APLL_CON); - } while (!(reg & (0x1 << 29))); - - /* - * 7. Change souce clock from SCLKMPLL(667Mhz) - * to SCLKA2M(200Mhz) in MFC_MUX and G3D MUX - * (667/4=166)->(200/4=50)Mhz - */ - reg = __raw_readl(S5P_CLK_SRC2); - reg &= ~(S5P_CLKSRC2_G3D_MASK | S5P_CLKSRC2_MFC_MASK); - reg |= (0 << S5P_CLKSRC2_G3D_SHIFT) | - (0 << S5P_CLKSRC2_MFC_SHIFT); - __raw_writel(reg, S5P_CLK_SRC2); - - do { - reg = __raw_readl(S5P_CLKMUX_STAT1); - } while (reg & ((1 << 7) | (1 << 3))); - - /* - * 8. Change divider for MFC and G3D - * (200/4=50)->(200/1=200)Mhz - */ - reg = __raw_readl(S5P_CLK_DIV2); - reg &= ~(S5P_CLKDIV2_G3D_MASK | S5P_CLKDIV2_MFC_MASK); - reg |= (clkdiv_val[index][10] << S5P_CLKDIV2_G3D_SHIFT) | - (clkdiv_val[index][9] << S5P_CLKDIV2_MFC_SHIFT); - __raw_writel(reg, S5P_CLK_DIV2); - - /* For MFC, G3D dividing */ - do { - reg = __raw_readl(S5P_CLKDIV_STAT0); - } while (reg & ((1 << 16) | (1 << 17))); - - /* 9. Change MPLL to APLL in MSYS_MUX */ - reg = __raw_readl(S5P_CLK_SRC0); - reg &= ~(S5P_CLKSRC0_MUX200_MASK); - reg |= (0x0 << S5P_CLKSRC0_MUX200_SHIFT); - __raw_writel(reg, S5P_CLK_SRC0); - - do { - reg = __raw_readl(S5P_CLKMUX_STAT0); - } while (reg & (0x1 << 18)); - - /* - * 10. DMC1 refresh counter - * L4 : DMC1 = 100Mhz 7.8us/(1/100) = 0x30c - * Others : DMC1 = 200Mhz 7.8us/(1/200) = 0x618 - */ - if (!bus_speed_changing) - s5pv210_set_refresh(DMC1, 200000); - } - - /* - * L4 level need to change memory bus speed, hence onedram clock divier - * and memory refresh parameter should be changed - */ - if (bus_speed_changing) { - reg = __raw_readl(S5P_CLK_DIV6); - reg &= ~S5P_CLKDIV6_ONEDRAM_MASK; - reg |= (clkdiv_val[index][8] << S5P_CLKDIV6_ONEDRAM_SHIFT); - __raw_writel(reg, S5P_CLK_DIV6); - - do { - reg = __raw_readl(S5P_CLKDIV_STAT1); - } while (reg & (1 << 15)); - - /* Reconfigure DRAM refresh counter value */ - if (index != L4) { - /* - * DMC0 : 166Mhz - * DMC1 : 200Mhz - */ - s5pv210_set_refresh(DMC0, 166000); - s5pv210_set_refresh(DMC1, 200000); - } else { - /* - * DMC0 : 83Mhz - * DMC1 : 100Mhz - */ - s5pv210_set_refresh(DMC0, 83000); - s5pv210_set_refresh(DMC1, 100000); - } - } - - if (freqs.new < freqs.old) { - /* Voltage down: will be implemented */ - } - - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - - printk(KERN_DEBUG "Perf changed[L%d]\n", index); - - return 0; -} - -#ifdef CONFIG_PM -static int s5pv210_cpufreq_suspend(struct cpufreq_policy *policy) -{ - return 0; -} - -static int s5pv210_cpufreq_resume(struct cpufreq_policy *policy) -{ - return 0; -} -#endif - -static int check_mem_type(void __iomem *dmc_reg) -{ - unsigned long val; - - val = __raw_readl(dmc_reg + 0x4); - val = (val & (0xf << 8)); - - return val >> 8; -} - -static int __init s5pv210_cpu_init(struct cpufreq_policy *policy) -{ - unsigned long mem_type; - - cpu_clk = clk_get(NULL, "armclk"); - if (IS_ERR(cpu_clk)) - return PTR_ERR(cpu_clk); - - dmc0_clk = clk_get(NULL, "sclk_dmc0"); - if (IS_ERR(dmc0_clk)) { - clk_put(cpu_clk); - return PTR_ERR(dmc0_clk); - } - - dmc1_clk = clk_get(NULL, "hclk_msys"); - if (IS_ERR(dmc1_clk)) { - clk_put(dmc0_clk); - clk_put(cpu_clk); - return PTR_ERR(dmc1_clk); - } - - if (policy->cpu != 0) - return -EINVAL; - - /* - * check_mem_type : This driver only support LPDDR & LPDDR2. - * other memory type is not supported. - */ - mem_type = check_mem_type(S5P_VA_DMC0); - - if ((mem_type != LPDDR) && (mem_type != LPDDR2)) { - printk(KERN_ERR "CPUFreq doesn't support this memory type\n"); - return -EINVAL; - } - - /* Find current refresh counter and frequency each DMC */ - s5pv210_dram_conf[0].refresh = (__raw_readl(S5P_VA_DMC0 + 0x30) * 1000); - s5pv210_dram_conf[0].freq = clk_get_rate(dmc0_clk); - - s5pv210_dram_conf[1].refresh = (__raw_readl(S5P_VA_DMC1 + 0x30) * 1000); - s5pv210_dram_conf[1].freq = clk_get_rate(dmc1_clk); - - policy->cur = policy->min = policy->max = s5pv210_getspeed(0); - - cpufreq_frequency_table_get_attr(s5pv210_freq_table, policy->cpu); - - policy->cpuinfo.transition_latency = 40000; - - return cpufreq_frequency_table_cpuinfo(policy, s5pv210_freq_table); -} - -static struct cpufreq_driver s5pv210_driver = { - .flags = CPUFREQ_STICKY, - .verify = s5pv210_verify_speed, - .target = s5pv210_target, - .get = s5pv210_getspeed, - .init = s5pv210_cpu_init, - .name = "s5pv210", -#ifdef CONFIG_PM - .suspend = s5pv210_cpufreq_suspend, - .resume = s5pv210_cpufreq_resume, -#endif -}; - -static int __init s5pv210_cpufreq_init(void) -{ - return cpufreq_register_driver(&s5pv210_driver); -} - -late_initcall(s5pv210_cpufreq_init); diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index 9fb8485..e898215 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -184,5 +184,10 @@ depends on X86 source "drivers/cpufreq/Kconfig.x86" endmenu +menu "ARM CPU frequency scaling drivers" +depends on ARM +source "drivers/cpufreq/Kconfig.arm" +endmenu + endif endmenu diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm new file mode 100644 index 0000000..e5c56c7 --- /dev/null +++ b/drivers/cpufreq/Kconfig.arm @@ -0,0 +1,23 @@ +# +# ARM CPU Frequency scaling drivers +# + +config ARM_S5PV210_CPUFREQ + bool "Samsung S5PV210 and S5PC110" + depends on CPU_S5PV210 + default y + help + This adds the CPUFreq driver for Samsung S5PV210 and + S5PC110 SoCs. + + If in doubt, say N. + +config ARM_EXYNOS4210_CPUFREQ + bool "Samsung EXYNOS4210" + depends on CPU_EXYNOS4210 + default y + help + This adds the CPUFreq driver for Samsung EXYNOS4210 + SoC (S5PV310 or S5PC210). + + If in doubt, say N. diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 0fd8cae..9922294 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -42,3 +42,5 @@ obj-$(CONFIG_X86_CPUFREQ_NFORCE2) += cpufreq-nforce2.o # ARM SoC drivers obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o obj-$(CONFIG_CPU_FREQ_S3C64XX) += s3c64xx.o +obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o +obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o diff --git a/drivers/cpufreq/exynos4210-cpufreq.c b/drivers/cpufreq/exynos4210-cpufreq.c new file mode 100644 index 0000000..54025fc --- /dev/null +++ b/drivers/cpufreq/exynos4210-cpufreq.c @@ -0,0 +1,568 @@ +/* + * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * EXYNOS4 - 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 +#include + +#include +#include + +static struct clk *cpu_clk; +static struct clk *moutcore; +static struct clk *mout_mpll; +static struct clk *mout_apll; + +static struct regulator *arm_regulator; +static struct regulator *int_regulator; + +static struct cpufreq_freqs freqs; +static unsigned int memtype; + +enum exynos4_memory_type { + DDR2 = 4, + LPDDR2, + DDR3, +}; + +enum cpufreq_level_index { + L0, L1, L2, L3, CPUFREQ_LEVEL_END, +}; + +static struct cpufreq_frequency_table exynos4_freq_table[] = { + {L0, 1000*1000}, + {L1, 800*1000}, + {L2, 400*1000}, + {L3, 100*1000}, + {0, CPUFREQ_TABLE_END}, +}; + +static unsigned int clkdiv_cpu0[CPUFREQ_LEVEL_END][7] = { + /* + * Clock divider value for following + * { DIVCORE, DIVCOREM0, DIVCOREM1, DIVPERIPH, + * DIVATB, DIVPCLK_DBG, DIVAPLL } + */ + + /* ARM L0: 1000MHz */ + { 0, 3, 7, 3, 3, 0, 1 }, + + /* ARM L1: 800MHz */ + { 0, 3, 7, 3, 3, 0, 1 }, + + /* ARM L2: 400MHz */ + { 0, 1, 3, 1, 3, 0, 1 }, + + /* ARM L3: 100MHz */ + { 0, 0, 1, 0, 3, 1, 1 }, +}; + +static unsigned int clkdiv_cpu1[CPUFREQ_LEVEL_END][2] = { + /* + * Clock divider value for following + * { DIVCOPY, DIVHPM } + */ + + /* ARM L0: 1000MHz */ + { 3, 0 }, + + /* ARM L1: 800MHz */ + { 3, 0 }, + + /* ARM L2: 400MHz */ + { 3, 0 }, + + /* ARM L3: 100MHz */ + { 3, 0 }, +}; + +static unsigned int clkdiv_dmc0[CPUFREQ_LEVEL_END][8] = { + /* + * Clock divider value for following + * { DIVACP, DIVACP_PCLK, DIVDPHY, DIVDMC, DIVDMCD + * DIVDMCP, DIVCOPY2, DIVCORE_TIMERS } + */ + + /* DMC L0: 400MHz */ + { 3, 1, 1, 1, 1, 1, 3, 1 }, + + /* DMC L1: 400MHz */ + { 3, 1, 1, 1, 1, 1, 3, 1 }, + + /* DMC L2: 266.7MHz */ + { 7, 1, 1, 2, 1, 1, 3, 1 }, + + /* DMC L3: 200MHz */ + { 7, 1, 1, 3, 1, 1, 3, 1 }, +}; + +static unsigned int clkdiv_top[CPUFREQ_LEVEL_END][5] = { + /* + * Clock divider value for following + * { DIVACLK200, DIVACLK100, DIVACLK160, DIVACLK133, DIVONENAND } + */ + + /* ACLK200 L0: 200MHz */ + { 3, 7, 4, 5, 1 }, + + /* ACLK200 L1: 200MHz */ + { 3, 7, 4, 5, 1 }, + + /* ACLK200 L2: 160MHz */ + { 4, 7, 5, 7, 1 }, + + /* ACLK200 L3: 133.3MHz */ + { 5, 7, 7, 7, 1 }, +}; + +static unsigned int clkdiv_lr_bus[CPUFREQ_LEVEL_END][2] = { + /* + * Clock divider value for following + * { DIVGDL/R, DIVGPL/R } + */ + + /* ACLK_GDL/R L0: 200MHz */ + { 3, 1 }, + + /* ACLK_GDL/R L1: 200MHz */ + { 3, 1 }, + + /* ACLK_GDL/R L2: 160MHz */ + { 4, 1 }, + + /* ACLK_GDL/R L3: 133.3MHz */ + { 5, 1 }, +}; + +struct cpufreq_voltage_table { + unsigned int index; /* any */ + unsigned int arm_volt; /* uV */ + unsigned int int_volt; +}; + +static struct cpufreq_voltage_table exynos4_volt_table[CPUFREQ_LEVEL_END] = { + { + .index = L0, + .arm_volt = 1200000, + .int_volt = 1100000, + }, { + .index = L1, + .arm_volt = 1100000, + .int_volt = 1100000, + }, { + .index = L2, + .arm_volt = 1000000, + .int_volt = 1000000, + }, { + .index = L3, + .arm_volt = 900000, + .int_volt = 1000000, + }, +}; + +static unsigned int exynos4_apll_pms_table[CPUFREQ_LEVEL_END] = { + /* APLL FOUT L0: 1000MHz */ + ((250 << 16) | (6 << 8) | 1), + + /* APLL FOUT L1: 800MHz */ + ((200 << 16) | (6 << 8) | 1), + + /* APLL FOUT L2 : 400MHz */ + ((200 << 16) | (6 << 8) | 2), + + /* APLL FOUT L3: 100MHz */ + ((200 << 16) | (6 << 8) | 4), +}; + +int exynos4_verify_speed(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, exynos4_freq_table); +} + +unsigned int exynos4_getspeed(unsigned int cpu) +{ + return clk_get_rate(cpu_clk) / 1000; +} + +void exynos4_set_clkdiv(unsigned int div_index) +{ + unsigned int tmp; + + /* Change Divider - CPU0 */ + + tmp = __raw_readl(S5P_CLKDIV_CPU); + + 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 |= ((clkdiv_cpu0[div_index][0] << S5P_CLKDIV_CPU0_CORE_SHIFT) | + (clkdiv_cpu0[div_index][1] << S5P_CLKDIV_CPU0_COREM0_SHIFT) | + (clkdiv_cpu0[div_index][2] << S5P_CLKDIV_CPU0_COREM1_SHIFT) | + (clkdiv_cpu0[div_index][3] << S5P_CLKDIV_CPU0_PERIPH_SHIFT) | + (clkdiv_cpu0[div_index][4] << S5P_CLKDIV_CPU0_ATB_SHIFT) | + (clkdiv_cpu0[div_index][5] << S5P_CLKDIV_CPU0_PCLKDBG_SHIFT) | + (clkdiv_cpu0[div_index][6] << S5P_CLKDIV_CPU0_APLL_SHIFT)); + + __raw_writel(tmp, S5P_CLKDIV_CPU); + + do { + tmp = __raw_readl(S5P_CLKDIV_STATCPU); + } while (tmp & 0x1111111); + + /* Change Divider - CPU1 */ + + tmp = __raw_readl(S5P_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); + + do { + tmp = __raw_readl(S5P_CLKDIV_STATCPU1); + } while (tmp & 0x11); + + /* Change Divider - DMC0 */ + + tmp = __raw_readl(S5P_CLKDIV_DMC0); + + tmp &= ~(S5P_CLKDIV_DMC0_ACP_MASK | S5P_CLKDIV_DMC0_ACPPCLK_MASK | + S5P_CLKDIV_DMC0_DPHY_MASK | S5P_CLKDIV_DMC0_DMC_MASK | + S5P_CLKDIV_DMC0_DMCD_MASK | S5P_CLKDIV_DMC0_DMCP_MASK | + S5P_CLKDIV_DMC0_COPY2_MASK | S5P_CLKDIV_DMC0_CORETI_MASK); + + tmp |= ((clkdiv_dmc0[div_index][0] << S5P_CLKDIV_DMC0_ACP_SHIFT) | + (clkdiv_dmc0[div_index][1] << S5P_CLKDIV_DMC0_ACPPCLK_SHIFT) | + (clkdiv_dmc0[div_index][2] << S5P_CLKDIV_DMC0_DPHY_SHIFT) | + (clkdiv_dmc0[div_index][3] << S5P_CLKDIV_DMC0_DMC_SHIFT) | + (clkdiv_dmc0[div_index][4] << S5P_CLKDIV_DMC0_DMCD_SHIFT) | + (clkdiv_dmc0[div_index][5] << S5P_CLKDIV_DMC0_DMCP_SHIFT) | + (clkdiv_dmc0[div_index][6] << S5P_CLKDIV_DMC0_COPY2_SHIFT) | + (clkdiv_dmc0[div_index][7] << S5P_CLKDIV_DMC0_CORETI_SHIFT)); + + __raw_writel(tmp, S5P_CLKDIV_DMC0); + + do { + tmp = __raw_readl(S5P_CLKDIV_STAT_DMC0); + } while (tmp & 0x11111111); + + /* Change Divider - TOP */ + + tmp = __raw_readl(S5P_CLKDIV_TOP); + + tmp &= ~(S5P_CLKDIV_TOP_ACLK200_MASK | S5P_CLKDIV_TOP_ACLK100_MASK | + S5P_CLKDIV_TOP_ACLK160_MASK | S5P_CLKDIV_TOP_ACLK133_MASK | + S5P_CLKDIV_TOP_ONENAND_MASK); + + tmp |= ((clkdiv_top[div_index][0] << S5P_CLKDIV_TOP_ACLK200_SHIFT) | + (clkdiv_top[div_index][1] << S5P_CLKDIV_TOP_ACLK100_SHIFT) | + (clkdiv_top[div_index][2] << S5P_CLKDIV_TOP_ACLK160_SHIFT) | + (clkdiv_top[div_index][3] << S5P_CLKDIV_TOP_ACLK133_SHIFT) | + (clkdiv_top[div_index][4] << S5P_CLKDIV_TOP_ONENAND_SHIFT)); + + __raw_writel(tmp, S5P_CLKDIV_TOP); + + do { + tmp = __raw_readl(S5P_CLKDIV_STAT_TOP); + } while (tmp & 0x11111); + + /* Change Divider - LEFTBUS */ + + tmp = __raw_readl(S5P_CLKDIV_LEFTBUS); + + tmp &= ~(S5P_CLKDIV_BUS_GDLR_MASK | S5P_CLKDIV_BUS_GPLR_MASK); + + tmp |= ((clkdiv_lr_bus[div_index][0] << S5P_CLKDIV_BUS_GDLR_SHIFT) | + (clkdiv_lr_bus[div_index][1] << S5P_CLKDIV_BUS_GPLR_SHIFT)); + + __raw_writel(tmp, S5P_CLKDIV_LEFTBUS); + + do { + tmp = __raw_readl(S5P_CLKDIV_STAT_LEFTBUS); + } while (tmp & 0x11); + + /* Change Divider - RIGHTBUS */ + + tmp = __raw_readl(S5P_CLKDIV_RIGHTBUS); + + tmp &= ~(S5P_CLKDIV_BUS_GDLR_MASK | S5P_CLKDIV_BUS_GPLR_MASK); + + tmp |= ((clkdiv_lr_bus[div_index][0] << S5P_CLKDIV_BUS_GDLR_SHIFT) | + (clkdiv_lr_bus[div_index][1] << S5P_CLKDIV_BUS_GPLR_SHIFT)); + + __raw_writel(tmp, S5P_CLKDIV_RIGHTBUS); + + do { + tmp = __raw_readl(S5P_CLKDIV_STAT_RIGHTBUS); + } while (tmp & 0x11); +} + +static void exynos4_set_apll(unsigned int index) +{ + unsigned int tmp; + + /* 1. MUX_CORE_SEL = MPLL, ARMCLK uses MPLL for lock time */ + clk_set_parent(moutcore, mout_mpll); + + do { + tmp = (__raw_readl(S5P_CLKMUX_STATCPU) + >> S5P_CLKSRC_CPU_MUXCORE_SHIFT); + tmp &= 0x7; + } while (tmp != 0x2); + + /* 2. Set APLL Lock time */ + __raw_writel(S5P_APLL_LOCKTIME, S5P_APLL_LOCK); + + /* 3. Change PLL PMS values */ + tmp = __raw_readl(S5P_APLL_CON0); + tmp &= ~((0x3ff << 16) | (0x3f << 8) | (0x7 << 0)); + tmp |= exynos4_apll_pms_table[index]; + __raw_writel(tmp, S5P_APLL_CON0); + + /* 4. wait_lock_time */ + do { + tmp = __raw_readl(S5P_APLL_CON0); + } while (!(tmp & (0x1 << S5P_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)); +} + +static void exynos4_set_frequency(unsigned int old_index, unsigned int new_index) +{ + unsigned int tmp; + + if (old_index > new_index) { + /* The frequency changing to L0 needs to change apll */ + if (freqs.new == exynos4_freq_table[L0].frequency) { + /* 1. Change the system clock divider values */ + exynos4_set_clkdiv(new_index); + + /* 2. Change the apll m,p,s value */ + exynos4_set_apll(new_index); + } else { + /* 1. Change the system clock divider values */ + exynos4_set_clkdiv(new_index); + + /* 2. Change just s value in apll m,p,s value */ + tmp = __raw_readl(S5P_APLL_CON0); + tmp &= ~(0x7 << 0); + tmp |= (exynos4_apll_pms_table[new_index] & 0x7); + __raw_writel(tmp, S5P_APLL_CON0); + } + } + + else if (old_index < new_index) { + /* The frequency changing from L0 needs to change apll */ + if (freqs.old == exynos4_freq_table[L0].frequency) { + /* 1. Change the apll m,p,s value */ + exynos4_set_apll(new_index); + + /* 2. Change the system clock divider values */ + exynos4_set_clkdiv(new_index); + } else { + /* 1. Change just s value in apll m,p,s value */ + tmp = __raw_readl(S5P_APLL_CON0); + tmp &= ~(0x7 << 0); + tmp |= (exynos4_apll_pms_table[new_index] & 0x7); + __raw_writel(tmp, S5P_APLL_CON0); + + /* 2. Change the system clock divider values */ + exynos4_set_clkdiv(new_index); + } + } +} + +static int exynos4_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int index, old_index; + unsigned int arm_volt, int_volt; + + freqs.old = exynos4_getspeed(policy->cpu); + + if (cpufreq_frequency_table_target(policy, exynos4_freq_table, + freqs.old, relation, &old_index)) + return -EINVAL; + + if (cpufreq_frequency_table_target(policy, exynos4_freq_table, + target_freq, relation, &index)) + return -EINVAL; + + freqs.new = exynos4_freq_table[index].frequency; + freqs.cpu = policy->cpu; + + if (freqs.new == freqs.old) + return 0; + + /* get the voltage value */ + arm_volt = exynos4_volt_table[index].arm_volt; + int_volt = exynos4_volt_table[index].int_volt; + + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + + /* control regulator */ + if (freqs.new > freqs.old) { + /* Voltage up */ + regulator_set_voltage(arm_regulator, arm_volt, arm_volt); + regulator_set_voltage(int_regulator, int_volt, int_volt); + } + + /* Clock Configuration Procedure */ + exynos4_set_frequency(old_index, index); + + /* control regulator */ + if (freqs.new < freqs.old) { + /* Voltage down */ + regulator_set_voltage(arm_regulator, arm_volt, arm_volt); + regulator_set_voltage(int_regulator, int_volt, int_volt); + } + + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + return 0; +} + +#ifdef CONFIG_PM +static int exynos4_cpufreq_suspend(struct cpufreq_policy *policy) +{ + return 0; +} + +static int exynos4_cpufreq_resume(struct cpufreq_policy *policy) +{ + return 0; +} +#endif + +static int exynos4_cpufreq_cpu_init(struct cpufreq_policy *policy) +{ + policy->cur = policy->min = policy->max = exynos4_getspeed(policy->cpu); + + cpufreq_frequency_table_get_attr(exynos4_freq_table, policy->cpu); + + /* set the transition latency value */ + policy->cpuinfo.transition_latency = 100000; + + /* + * EXYNOS4 multi-core processors has 2 cores + * that the frequency cannot be set independently. + * Each cpu is bound to the same speed. + * So the affected cpu is all of the cpus. + */ + cpumask_setall(policy->cpus); + + return cpufreq_frequency_table_cpuinfo(policy, exynos4_freq_table); +} + +static struct cpufreq_driver exynos4_driver = { + .flags = CPUFREQ_STICKY, + .verify = exynos4_verify_speed, + .target = exynos4_target, + .get = exynos4_getspeed, + .init = exynos4_cpufreq_cpu_init, + .name = "exynos4_cpufreq", +#ifdef CONFIG_PM + .suspend = exynos4_cpufreq_suspend, + .resume = exynos4_cpufreq_resume, +#endif +}; + +static int __init exynos4_cpufreq_init(void) +{ + 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 out; + + mout_mpll = clk_get(NULL, "mout_mpll"); + if (IS_ERR(mout_mpll)) + goto out; + + mout_apll = clk_get(NULL, "mout_apll"); + if (IS_ERR(mout_apll)) + goto out; + + arm_regulator = regulator_get(NULL, "vdd_arm"); + if (IS_ERR(arm_regulator)) { + printk(KERN_ERR "failed to get resource %s\n", "vdd_arm"); + goto out; + } + + int_regulator = regulator_get(NULL, "vdd_int"); + if (IS_ERR(int_regulator)) { + printk(KERN_ERR "failed to get resource %s\n", "vdd_int"); + goto out; + } + + /* + * Check DRAM type. + * Because DVFS level is different according to DRAM type. + */ + memtype = __raw_readl(S5P_VA_DMC0 + S5P_DMC0_MEMCON_OFFSET); + memtype = (memtype >> S5P_DMC0_MEMTYPE_SHIFT); + memtype &= S5P_DMC0_MEMTYPE_MASK; + + if ((memtype < DDR2) && (memtype > DDR3)) { + printk(KERN_ERR "%s: wrong memtype= 0x%x\n", __func__, memtype); + goto out; + } else { + printk(KERN_DEBUG "%s: memtype= 0x%x\n", __func__, memtype); + } + + return cpufreq_register_driver(&exynos4_driver); + +out: + if (!IS_ERR(cpu_clk)) + clk_put(cpu_clk); + + if (!IS_ERR(moutcore)) + clk_put(moutcore); + + if (!IS_ERR(mout_mpll)) + clk_put(mout_mpll); + + if (!IS_ERR(mout_apll)) + clk_put(mout_apll); + + if (!IS_ERR(arm_regulator)) + regulator_put(arm_regulator); + + if (!IS_ERR(int_regulator)) + regulator_put(int_regulator); + + printk(KERN_ERR "%s: failed initialization\n", __func__); + + return -EINVAL; +} +late_initcall(exynos4_cpufreq_init); diff --git a/drivers/cpufreq/s5pv210-cpufreq.c b/drivers/cpufreq/s5pv210-cpufreq.c new file mode 100644 index 0000000..ea35d3f --- /dev/null +++ b/drivers/cpufreq/s5pv210-cpufreq.c @@ -0,0 +1,484 @@ +/* + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * CPU frequency scaling for S5PC110/S5PV210 + * + * 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 + +static struct clk *cpu_clk; +static struct clk *dmc0_clk; +static struct clk *dmc1_clk; +static struct cpufreq_freqs freqs; + +/* APLL M,P,S values for 1G/800Mhz */ +#define APLL_VAL_1000 ((1 << 31) | (125 << 16) | (3 << 8) | 1) +#define APLL_VAL_800 ((1 << 31) | (100 << 16) | (3 << 8) | 1) + +/* + * DRAM configurations to calculate refresh counter for changing + * frequency of memory. + */ +struct dram_conf { + unsigned long freq; /* HZ */ + unsigned long refresh; /* DRAM refresh counter * 1000 */ +}; + +/* DRAM configuration (DMC0 and DMC1) */ +static struct dram_conf s5pv210_dram_conf[2]; + +enum perf_level { + L0, L1, L2, L3, L4, +}; + +enum s5pv210_mem_type { + LPDDR = 0x1, + LPDDR2 = 0x2, + DDR2 = 0x4, +}; + +enum s5pv210_dmc_port { + DMC0 = 0, + DMC1, +}; + +static struct cpufreq_frequency_table s5pv210_freq_table[] = { + {L0, 1000*1000}, + {L1, 800*1000}, + {L2, 400*1000}, + {L3, 200*1000}, + {L4, 100*1000}, + {0, CPUFREQ_TABLE_END}, +}; + +static u32 clkdiv_val[5][11] = { + /* + * Clock divider value for following + * { APLL, A2M, HCLK_MSYS, PCLK_MSYS, + * HCLK_DSYS, PCLK_DSYS, HCLK_PSYS, PCLK_PSYS, + * ONEDRAM, MFC, G3D } + */ + + /* L0 : [1000/200/100][166/83][133/66][200/200] */ + {0, 4, 4, 1, 3, 1, 4, 1, 3, 0, 0}, + + /* L1 : [800/200/100][166/83][133/66][200/200] */ + {0, 3, 3, 1, 3, 1, 4, 1, 3, 0, 0}, + + /* L2 : [400/200/100][166/83][133/66][200/200] */ + {1, 3, 1, 1, 3, 1, 4, 1, 3, 0, 0}, + + /* L3 : [200/200/100][166/83][133/66][200/200] */ + {3, 3, 1, 1, 3, 1, 4, 1, 3, 0, 0}, + + /* L4 : [100/100/100][83/83][66/66][100/100] */ + {7, 7, 0, 0, 7, 0, 9, 0, 7, 0, 0}, +}; + +/* + * This function set DRAM refresh counter + * accoriding to operating frequency of DRAM + * ch: DMC port number 0 or 1 + * freq: Operating frequency of DRAM(KHz) + */ +static void s5pv210_set_refresh(enum s5pv210_dmc_port ch, unsigned long freq) +{ + unsigned long tmp, tmp1; + void __iomem *reg = NULL; + + if (ch == DMC0) { + reg = (S5P_VA_DMC0 + 0x30); + } else if (ch == DMC1) { + reg = (S5P_VA_DMC1 + 0x30); + } else { + printk(KERN_ERR "Cannot find DMC port\n"); + return; + } + + /* Find current DRAM frequency */ + tmp = s5pv210_dram_conf[ch].freq; + + do_div(tmp, freq); + + tmp1 = s5pv210_dram_conf[ch].refresh; + + do_div(tmp1, tmp); + + __raw_writel(tmp1, reg); +} + +int s5pv210_verify_speed(struct cpufreq_policy *policy) +{ + if (policy->cpu) + return -EINVAL; + + return cpufreq_frequency_table_verify(policy, s5pv210_freq_table); +} + +unsigned int s5pv210_getspeed(unsigned int cpu) +{ + if (cpu) + return 0; + + return clk_get_rate(cpu_clk) / 1000; +} + +static int s5pv210_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned long reg; + unsigned int index, priv_index; + unsigned int pll_changing = 0; + unsigned int bus_speed_changing = 0; + + freqs.old = s5pv210_getspeed(0); + + if (cpufreq_frequency_table_target(policy, s5pv210_freq_table, + target_freq, relation, &index)) + return -EINVAL; + + freqs.new = s5pv210_freq_table[index].frequency; + freqs.cpu = 0; + + if (freqs.new == freqs.old) + return 0; + + /* Finding current running level index */ + if (cpufreq_frequency_table_target(policy, s5pv210_freq_table, + freqs.old, relation, &priv_index)) + return -EINVAL; + + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + + if (freqs.new > freqs.old) { + /* Voltage up: will be implemented */ + } + + /* Check if there need to change PLL */ + if ((index == L0) || (priv_index == L0)) + pll_changing = 1; + + /* Check if there need to change System bus clock */ + if ((index == L4) || (priv_index == L4)) + bus_speed_changing = 1; + + if (bus_speed_changing) { + /* + * Reconfigure DRAM refresh counter value for minimum + * temporary clock while changing divider. + * expected clock is 83Mhz : 7.8usec/(1/83Mhz) = 0x287 + */ + if (pll_changing) + s5pv210_set_refresh(DMC1, 83000); + else + s5pv210_set_refresh(DMC1, 100000); + + s5pv210_set_refresh(DMC0, 83000); + } + + /* + * APLL should be changed in this level + * APLL -> MPLL(for stable transition) -> APLL + * Some clock source's clock API are not prepared. + * Do not use clock API in below code. + */ + if (pll_changing) { + /* + * 1. Temporary Change divider for MFC and G3D + * SCLKA2M(200/1=200)->(200/4=50)Mhz + */ + reg = __raw_readl(S5P_CLK_DIV2); + reg &= ~(S5P_CLKDIV2_G3D_MASK | S5P_CLKDIV2_MFC_MASK); + reg |= (3 << S5P_CLKDIV2_G3D_SHIFT) | + (3 << S5P_CLKDIV2_MFC_SHIFT); + __raw_writel(reg, S5P_CLK_DIV2); + + /* For MFC, G3D dividing */ + do { + reg = __raw_readl(S5P_CLKDIV_STAT0); + } while (reg & ((1 << 16) | (1 << 17))); + + /* + * 2. Change SCLKA2M(200Mhz)to SCLKMPLL in MFC_MUX, G3D MUX + * (200/4=50)->(667/4=166)Mhz + */ + reg = __raw_readl(S5P_CLK_SRC2); + reg &= ~(S5P_CLKSRC2_G3D_MASK | S5P_CLKSRC2_MFC_MASK); + reg |= (1 << S5P_CLKSRC2_G3D_SHIFT) | + (1 << S5P_CLKSRC2_MFC_SHIFT); + __raw_writel(reg, S5P_CLK_SRC2); + + do { + reg = __raw_readl(S5P_CLKMUX_STAT1); + } while (reg & ((1 << 7) | (1 << 3))); + + /* + * 3. DMC1 refresh count for 133Mhz if (index == L4) is + * true refresh counter is already programed in upper + * code. 0x287@83Mhz + */ + if (!bus_speed_changing) + s5pv210_set_refresh(DMC1, 133000); + + /* 4. SCLKAPLL -> SCLKMPLL */ + reg = __raw_readl(S5P_CLK_SRC0); + reg &= ~(S5P_CLKSRC0_MUX200_MASK); + reg |= (0x1 << S5P_CLKSRC0_MUX200_SHIFT); + __raw_writel(reg, S5P_CLK_SRC0); + + do { + reg = __raw_readl(S5P_CLKMUX_STAT0); + } while (reg & (0x1 << 18)); + + } + + /* Change divider */ + reg = __raw_readl(S5P_CLK_DIV0); + + reg &= ~(S5P_CLKDIV0_APLL_MASK | S5P_CLKDIV0_A2M_MASK | + S5P_CLKDIV0_HCLK200_MASK | S5P_CLKDIV0_PCLK100_MASK | + S5P_CLKDIV0_HCLK166_MASK | S5P_CLKDIV0_PCLK83_MASK | + S5P_CLKDIV0_HCLK133_MASK | S5P_CLKDIV0_PCLK66_MASK); + + reg |= ((clkdiv_val[index][0] << S5P_CLKDIV0_APLL_SHIFT) | + (clkdiv_val[index][1] << S5P_CLKDIV0_A2M_SHIFT) | + (clkdiv_val[index][2] << S5P_CLKDIV0_HCLK200_SHIFT) | + (clkdiv_val[index][3] << S5P_CLKDIV0_PCLK100_SHIFT) | + (clkdiv_val[index][4] << S5P_CLKDIV0_HCLK166_SHIFT) | + (clkdiv_val[index][5] << S5P_CLKDIV0_PCLK83_SHIFT) | + (clkdiv_val[index][6] << S5P_CLKDIV0_HCLK133_SHIFT) | + (clkdiv_val[index][7] << S5P_CLKDIV0_PCLK66_SHIFT)); + + __raw_writel(reg, S5P_CLK_DIV0); + + do { + reg = __raw_readl(S5P_CLKDIV_STAT0); + } while (reg & 0xff); + + /* ARM MCS value changed */ + reg = __raw_readl(S5P_ARM_MCS_CON); + reg &= ~0x3; + if (index >= L3) + reg |= 0x3; + else + reg |= 0x1; + + __raw_writel(reg, S5P_ARM_MCS_CON); + + if (pll_changing) { + /* 5. Set Lock time = 30us*24Mhz = 0x2cf */ + __raw_writel(0x2cf, S5P_APLL_LOCK); + + /* + * 6. Turn on APLL + * 6-1. Set PMS values + * 6-2. Wait untile the PLL is locked + */ + if (index == L0) + __raw_writel(APLL_VAL_1000, S5P_APLL_CON); + else + __raw_writel(APLL_VAL_800, S5P_APLL_CON); + + do { + reg = __raw_readl(S5P_APLL_CON); + } while (!(reg & (0x1 << 29))); + + /* + * 7. Change souce clock from SCLKMPLL(667Mhz) + * to SCLKA2M(200Mhz) in MFC_MUX and G3D MUX + * (667/4=166)->(200/4=50)Mhz + */ + reg = __raw_readl(S5P_CLK_SRC2); + reg &= ~(S5P_CLKSRC2_G3D_MASK | S5P_CLKSRC2_MFC_MASK); + reg |= (0 << S5P_CLKSRC2_G3D_SHIFT) | + (0 << S5P_CLKSRC2_MFC_SHIFT); + __raw_writel(reg, S5P_CLK_SRC2); + + do { + reg = __raw_readl(S5P_CLKMUX_STAT1); + } while (reg & ((1 << 7) | (1 << 3))); + + /* + * 8. Change divider for MFC and G3D + * (200/4=50)->(200/1=200)Mhz + */ + reg = __raw_readl(S5P_CLK_DIV2); + reg &= ~(S5P_CLKDIV2_G3D_MASK | S5P_CLKDIV2_MFC_MASK); + reg |= (clkdiv_val[index][10] << S5P_CLKDIV2_G3D_SHIFT) | + (clkdiv_val[index][9] << S5P_CLKDIV2_MFC_SHIFT); + __raw_writel(reg, S5P_CLK_DIV2); + + /* For MFC, G3D dividing */ + do { + reg = __raw_readl(S5P_CLKDIV_STAT0); + } while (reg & ((1 << 16) | (1 << 17))); + + /* 9. Change MPLL to APLL in MSYS_MUX */ + reg = __raw_readl(S5P_CLK_SRC0); + reg &= ~(S5P_CLKSRC0_MUX200_MASK); + reg |= (0x0 << S5P_CLKSRC0_MUX200_SHIFT); + __raw_writel(reg, S5P_CLK_SRC0); + + do { + reg = __raw_readl(S5P_CLKMUX_STAT0); + } while (reg & (0x1 << 18)); + + /* + * 10. DMC1 refresh counter + * L4 : DMC1 = 100Mhz 7.8us/(1/100) = 0x30c + * Others : DMC1 = 200Mhz 7.8us/(1/200) = 0x618 + */ + if (!bus_speed_changing) + s5pv210_set_refresh(DMC1, 200000); + } + + /* + * L4 level need to change memory bus speed, hence onedram clock divier + * and memory refresh parameter should be changed + */ + if (bus_speed_changing) { + reg = __raw_readl(S5P_CLK_DIV6); + reg &= ~S5P_CLKDIV6_ONEDRAM_MASK; + reg |= (clkdiv_val[index][8] << S5P_CLKDIV6_ONEDRAM_SHIFT); + __raw_writel(reg, S5P_CLK_DIV6); + + do { + reg = __raw_readl(S5P_CLKDIV_STAT1); + } while (reg & (1 << 15)); + + /* Reconfigure DRAM refresh counter value */ + if (index != L4) { + /* + * DMC0 : 166Mhz + * DMC1 : 200Mhz + */ + s5pv210_set_refresh(DMC0, 166000); + s5pv210_set_refresh(DMC1, 200000); + } else { + /* + * DMC0 : 83Mhz + * DMC1 : 100Mhz + */ + s5pv210_set_refresh(DMC0, 83000); + s5pv210_set_refresh(DMC1, 100000); + } + } + + if (freqs.new < freqs.old) { + /* Voltage down: will be implemented */ + } + + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + printk(KERN_DEBUG "Perf changed[L%d]\n", index); + + return 0; +} + +#ifdef CONFIG_PM +static int s5pv210_cpufreq_suspend(struct cpufreq_policy *policy) +{ + return 0; +} + +static int s5pv210_cpufreq_resume(struct cpufreq_policy *policy) +{ + return 0; +} +#endif + +static int check_mem_type(void __iomem *dmc_reg) +{ + unsigned long val; + + val = __raw_readl(dmc_reg + 0x4); + val = (val & (0xf << 8)); + + return val >> 8; +} + +static int __init s5pv210_cpu_init(struct cpufreq_policy *policy) +{ + unsigned long mem_type; + + cpu_clk = clk_get(NULL, "armclk"); + if (IS_ERR(cpu_clk)) + return PTR_ERR(cpu_clk); + + dmc0_clk = clk_get(NULL, "sclk_dmc0"); + if (IS_ERR(dmc0_clk)) { + clk_put(cpu_clk); + return PTR_ERR(dmc0_clk); + } + + dmc1_clk = clk_get(NULL, "hclk_msys"); + if (IS_ERR(dmc1_clk)) { + clk_put(dmc0_clk); + clk_put(cpu_clk); + return PTR_ERR(dmc1_clk); + } + + if (policy->cpu != 0) + return -EINVAL; + + /* + * check_mem_type : This driver only support LPDDR & LPDDR2. + * other memory type is not supported. + */ + mem_type = check_mem_type(S5P_VA_DMC0); + + if ((mem_type != LPDDR) && (mem_type != LPDDR2)) { + printk(KERN_ERR "CPUFreq doesn't support this memory type\n"); + return -EINVAL; + } + + /* Find current refresh counter and frequency each DMC */ + s5pv210_dram_conf[0].refresh = (__raw_readl(S5P_VA_DMC0 + 0x30) * 1000); + s5pv210_dram_conf[0].freq = clk_get_rate(dmc0_clk); + + s5pv210_dram_conf[1].refresh = (__raw_readl(S5P_VA_DMC1 + 0x30) * 1000); + s5pv210_dram_conf[1].freq = clk_get_rate(dmc1_clk); + + policy->cur = policy->min = policy->max = s5pv210_getspeed(0); + + cpufreq_frequency_table_get_attr(s5pv210_freq_table, policy->cpu); + + policy->cpuinfo.transition_latency = 40000; + + return cpufreq_frequency_table_cpuinfo(policy, s5pv210_freq_table); +} + +static struct cpufreq_driver s5pv210_driver = { + .flags = CPUFREQ_STICKY, + .verify = s5pv210_verify_speed, + .target = s5pv210_target, + .get = s5pv210_getspeed, + .init = s5pv210_cpu_init, + .name = "s5pv210", +#ifdef CONFIG_PM + .suspend = s5pv210_cpufreq_suspend, + .resume = s5pv210_cpufreq_resume, +#endif +}; + +static int __init s5pv210_cpufreq_init(void) +{ + return cpufreq_register_driver(&s5pv210_driver); +} + +late_initcall(s5pv210_cpufreq_init); -- cgit v0.10.2 From 069283c3ec87e0abaa14f6bef96342b609f0fb92 Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Mon, 6 Jun 2011 21:10:04 -0400 Subject: [CPUFREQ] Remove some vi noise that escaped into the Makefile. diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 9922294..5419478 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -13,7 +13,7 @@ obj-$(CONFIG_CPU_FREQ_GOV_CONSERVATIVE) += cpufreq_conservative.o # CPUfreq cross-arch helpers obj-$(CONFIG_CPU_FREQ_TABLE) += freq_table.o -##################################################################################d +################################################################################## # x86 drivers. # Link order matters. K8 is preferred to ACPI because of firmware bugs in early # K8 systems. ACPI is preferred to all other hardware-specific drivers. @@ -37,8 +37,7 @@ obj-$(CONFIG_X86_SPEEDSTEP_CENTRINO) += speedstep-centrino.o obj-$(CONFIG_X86_P4_CLOCKMOD) += p4-clockmod.o obj-$(CONFIG_X86_CPUFREQ_NFORCE2) += cpufreq-nforce2.o -##################################################################################d - +################################################################################## # ARM SoC drivers obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o obj-$(CONFIG_CPU_FREQ_S3C64XX) += s3c64xx.o -- cgit v0.10.2 From 15964d388528c1dbb672027c8003fe7e81630a35 Mon Sep 17 00:00:00 2001 From: Kukjin Kim Date: Mon, 6 Jun 2011 18:43:01 -0700 Subject: [CPUFREQ] Move compile for S3C64XX cpufreq to /drivers/cpufreq Cc: Mark Brown Signed-off-by: Kukjin Kim Signed-off-by: Dave Jones diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 9adc278..31c2899 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -1875,10 +1875,6 @@ config CPU_FREQ_PXA default y select CPU_FREQ_DEFAULT_GOV_USERSPACE -config CPU_FREQ_S3C64XX - bool "CPUfreq support for Samsung S3C64XX CPUs" - depends on CPU_FREQ && CPU_S3C6410 - config CPU_FREQ_S3C bool help diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index e5c56c7..72a0044 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -2,6 +2,15 @@ # ARM CPU Frequency scaling drivers # +config ARM_S3C64XX_CPUFREQ + bool "Samsung S3C64XX" + depends on CPU_S3C6410 + default y + help + This adds the CPUFreq driver for Samsung S3C6410 SoC. + + If in doubt, say N. + config ARM_S5PV210_CPUFREQ bool "Samsung S5PV210 and S5PC110" depends on CPU_S5PV210 diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 5419478..ab75e57 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -40,6 +40,6 @@ obj-$(CONFIG_X86_CPUFREQ_NFORCE2) += cpufreq-nforce2.o ################################################################################## # ARM SoC drivers obj-$(CONFIG_UX500_SOC_DB8500) += db8500-cpufreq.o -obj-$(CONFIG_CPU_FREQ_S3C64XX) += s3c64xx.o +obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o diff --git a/drivers/cpufreq/s3c64xx-cpufreq.c b/drivers/cpufreq/s3c64xx-cpufreq.c new file mode 100644 index 0000000..fc3f180 --- /dev/null +++ b/drivers/cpufreq/s3c64xx-cpufreq.c @@ -0,0 +1,269 @@ +/* + * Copyright 2009 Wolfson Microelectronics plc + * + * S3C64xx CPUfreq 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 + +static struct clk *armclk; +static struct regulator *vddarm; +static unsigned long regulator_latency; + +#ifdef CONFIG_CPU_S3C6410 +struct s3c64xx_dvfs { + unsigned int vddarm_min; + unsigned int vddarm_max; +}; + +static struct s3c64xx_dvfs s3c64xx_dvfs_table[] = { + [0] = { 1000000, 1150000 }, + [1] = { 1050000, 1150000 }, + [2] = { 1100000, 1150000 }, + [3] = { 1200000, 1350000 }, +}; + +static struct cpufreq_frequency_table s3c64xx_freq_table[] = { + { 0, 66000 }, + { 0, 133000 }, + { 1, 222000 }, + { 1, 266000 }, + { 2, 333000 }, + { 2, 400000 }, + { 2, 532000 }, + { 2, 533000 }, + { 3, 667000 }, + { 0, CPUFREQ_TABLE_END }, +}; +#endif + +static int s3c64xx_cpufreq_verify_speed(struct cpufreq_policy *policy) +{ + if (policy->cpu != 0) + return -EINVAL; + + return cpufreq_frequency_table_verify(policy, s3c64xx_freq_table); +} + +static unsigned int s3c64xx_cpufreq_get_speed(unsigned int cpu) +{ + if (cpu != 0) + return 0; + + return clk_get_rate(armclk) / 1000; +} + +static int s3c64xx_cpufreq_set_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + int ret; + unsigned int i; + struct cpufreq_freqs freqs; + struct s3c64xx_dvfs *dvfs; + + ret = cpufreq_frequency_table_target(policy, s3c64xx_freq_table, + target_freq, relation, &i); + if (ret != 0) + return ret; + + freqs.cpu = 0; + freqs.old = clk_get_rate(armclk) / 1000; + freqs.new = s3c64xx_freq_table[i].frequency; + freqs.flags = 0; + dvfs = &s3c64xx_dvfs_table[s3c64xx_freq_table[i].index]; + + if (freqs.old == freqs.new) + return 0; + + pr_debug("cpufreq: Transition %d-%dkHz\n", freqs.old, freqs.new); + + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + +#ifdef CONFIG_REGULATOR + if (vddarm && freqs.new > freqs.old) { + ret = regulator_set_voltage(vddarm, + dvfs->vddarm_min, + dvfs->vddarm_max); + if (ret != 0) { + pr_err("cpufreq: Failed to set VDDARM for %dkHz: %d\n", + freqs.new, ret); + goto err; + } + } +#endif + + ret = clk_set_rate(armclk, freqs.new * 1000); + if (ret < 0) { + pr_err("cpufreq: Failed to set rate %dkHz: %d\n", + freqs.new, ret); + goto err; + } + +#ifdef CONFIG_REGULATOR + if (vddarm && freqs.new < freqs.old) { + ret = regulator_set_voltage(vddarm, + dvfs->vddarm_min, + dvfs->vddarm_max); + if (ret != 0) { + pr_err("cpufreq: Failed to set VDDARM for %dkHz: %d\n", + freqs.new, ret); + goto err_clk; + } + } +#endif + + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + pr_debug("cpufreq: Set actual frequency %lukHz\n", + clk_get_rate(armclk) / 1000); + + return 0; + +err_clk: + if (clk_set_rate(armclk, freqs.old * 1000) < 0) + pr_err("Failed to restore original clock rate\n"); +err: + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + return ret; +} + +#ifdef CONFIG_REGULATOR +static void __init s3c64xx_cpufreq_config_regulator(void) +{ + int count, v, i, found; + struct cpufreq_frequency_table *freq; + struct s3c64xx_dvfs *dvfs; + + count = regulator_count_voltages(vddarm); + if (count < 0) { + pr_err("cpufreq: Unable to check supported voltages\n"); + } + + freq = s3c64xx_freq_table; + while (count > 0 && freq->frequency != CPUFREQ_TABLE_END) { + if (freq->frequency == CPUFREQ_ENTRY_INVALID) + continue; + + dvfs = &s3c64xx_dvfs_table[freq->index]; + found = 0; + + for (i = 0; i < count; i++) { + v = regulator_list_voltage(vddarm, i); + if (v >= dvfs->vddarm_min && v <= dvfs->vddarm_max) + found = 1; + } + + if (!found) { + pr_debug("cpufreq: %dkHz unsupported by regulator\n", + freq->frequency); + freq->frequency = CPUFREQ_ENTRY_INVALID; + } + + freq++; + } + + /* Guess based on having to do an I2C/SPI write; in future we + * will be able to query the regulator performance here. */ + regulator_latency = 1 * 1000 * 1000; +} +#endif + +static int s3c64xx_cpufreq_driver_init(struct cpufreq_policy *policy) +{ + int ret; + struct cpufreq_frequency_table *freq; + + if (policy->cpu != 0) + return -EINVAL; + + if (s3c64xx_freq_table == NULL) { + pr_err("cpufreq: No frequency information for this CPU\n"); + return -ENODEV; + } + + armclk = clk_get(NULL, "armclk"); + if (IS_ERR(armclk)) { + pr_err("cpufreq: Unable to obtain ARMCLK: %ld\n", + PTR_ERR(armclk)); + return PTR_ERR(armclk); + } + +#ifdef CONFIG_REGULATOR + vddarm = regulator_get(NULL, "vddarm"); + if (IS_ERR(vddarm)) { + ret = PTR_ERR(vddarm); + pr_err("cpufreq: Failed to obtain VDDARM: %d\n", ret); + pr_err("cpufreq: Only frequency scaling available\n"); + vddarm = NULL; + } else { + s3c64xx_cpufreq_config_regulator(); + } +#endif + + freq = s3c64xx_freq_table; + while (freq->frequency != CPUFREQ_TABLE_END) { + unsigned long r; + + /* Check for frequencies we can generate */ + r = clk_round_rate(armclk, freq->frequency * 1000); + r /= 1000; + if (r != freq->frequency) { + pr_debug("cpufreq: %dkHz unsupported by clock\n", + freq->frequency); + freq->frequency = CPUFREQ_ENTRY_INVALID; + } + + /* If we have no regulator then assume startup + * frequency is the maximum we can support. */ + if (!vddarm && freq->frequency > s3c64xx_cpufreq_get_speed(0)) + freq->frequency = CPUFREQ_ENTRY_INVALID; + + freq++; + } + + policy->cur = clk_get_rate(armclk) / 1000; + + /* Datasheet says PLL stabalisation time (if we were to use + * the PLLs, which we don't currently) is ~300us worst case, + * but add some fudge. + */ + policy->cpuinfo.transition_latency = (500 * 1000) + regulator_latency; + + ret = cpufreq_frequency_table_cpuinfo(policy, s3c64xx_freq_table); + if (ret != 0) { + pr_err("cpufreq: Failed to configure frequency table: %d\n", + ret); + regulator_put(vddarm); + clk_put(armclk); + } + + return ret; +} + +static struct cpufreq_driver s3c64xx_cpufreq_driver = { + .owner = THIS_MODULE, + .flags = 0, + .verify = s3c64xx_cpufreq_verify_speed, + .target = s3c64xx_cpufreq_set_target, + .get = s3c64xx_cpufreq_get_speed, + .init = s3c64xx_cpufreq_driver_init, + .name = "s3c", +}; + +static int __init s3c64xx_cpufreq_init(void) +{ + return cpufreq_register_driver(&s3c64xx_cpufreq_driver); +} +module_init(s3c64xx_cpufreq_init); diff --git a/drivers/cpufreq/s3c64xx.c b/drivers/cpufreq/s3c64xx.c deleted file mode 100644 index fc3f180..0000000 --- a/drivers/cpufreq/s3c64xx.c +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright 2009 Wolfson Microelectronics plc - * - * S3C64xx CPUfreq 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 - -static struct clk *armclk; -static struct regulator *vddarm; -static unsigned long regulator_latency; - -#ifdef CONFIG_CPU_S3C6410 -struct s3c64xx_dvfs { - unsigned int vddarm_min; - unsigned int vddarm_max; -}; - -static struct s3c64xx_dvfs s3c64xx_dvfs_table[] = { - [0] = { 1000000, 1150000 }, - [1] = { 1050000, 1150000 }, - [2] = { 1100000, 1150000 }, - [3] = { 1200000, 1350000 }, -}; - -static struct cpufreq_frequency_table s3c64xx_freq_table[] = { - { 0, 66000 }, - { 0, 133000 }, - { 1, 222000 }, - { 1, 266000 }, - { 2, 333000 }, - { 2, 400000 }, - { 2, 532000 }, - { 2, 533000 }, - { 3, 667000 }, - { 0, CPUFREQ_TABLE_END }, -}; -#endif - -static int s3c64xx_cpufreq_verify_speed(struct cpufreq_policy *policy) -{ - if (policy->cpu != 0) - return -EINVAL; - - return cpufreq_frequency_table_verify(policy, s3c64xx_freq_table); -} - -static unsigned int s3c64xx_cpufreq_get_speed(unsigned int cpu) -{ - if (cpu != 0) - return 0; - - return clk_get_rate(armclk) / 1000; -} - -static int s3c64xx_cpufreq_set_target(struct cpufreq_policy *policy, - unsigned int target_freq, - unsigned int relation) -{ - int ret; - unsigned int i; - struct cpufreq_freqs freqs; - struct s3c64xx_dvfs *dvfs; - - ret = cpufreq_frequency_table_target(policy, s3c64xx_freq_table, - target_freq, relation, &i); - if (ret != 0) - return ret; - - freqs.cpu = 0; - freqs.old = clk_get_rate(armclk) / 1000; - freqs.new = s3c64xx_freq_table[i].frequency; - freqs.flags = 0; - dvfs = &s3c64xx_dvfs_table[s3c64xx_freq_table[i].index]; - - if (freqs.old == freqs.new) - return 0; - - pr_debug("cpufreq: Transition %d-%dkHz\n", freqs.old, freqs.new); - - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - -#ifdef CONFIG_REGULATOR - if (vddarm && freqs.new > freqs.old) { - ret = regulator_set_voltage(vddarm, - dvfs->vddarm_min, - dvfs->vddarm_max); - if (ret != 0) { - pr_err("cpufreq: Failed to set VDDARM for %dkHz: %d\n", - freqs.new, ret); - goto err; - } - } -#endif - - ret = clk_set_rate(armclk, freqs.new * 1000); - if (ret < 0) { - pr_err("cpufreq: Failed to set rate %dkHz: %d\n", - freqs.new, ret); - goto err; - } - -#ifdef CONFIG_REGULATOR - if (vddarm && freqs.new < freqs.old) { - ret = regulator_set_voltage(vddarm, - dvfs->vddarm_min, - dvfs->vddarm_max); - if (ret != 0) { - pr_err("cpufreq: Failed to set VDDARM for %dkHz: %d\n", - freqs.new, ret); - goto err_clk; - } - } -#endif - - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - - pr_debug("cpufreq: Set actual frequency %lukHz\n", - clk_get_rate(armclk) / 1000); - - return 0; - -err_clk: - if (clk_set_rate(armclk, freqs.old * 1000) < 0) - pr_err("Failed to restore original clock rate\n"); -err: - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - - return ret; -} - -#ifdef CONFIG_REGULATOR -static void __init s3c64xx_cpufreq_config_regulator(void) -{ - int count, v, i, found; - struct cpufreq_frequency_table *freq; - struct s3c64xx_dvfs *dvfs; - - count = regulator_count_voltages(vddarm); - if (count < 0) { - pr_err("cpufreq: Unable to check supported voltages\n"); - } - - freq = s3c64xx_freq_table; - while (count > 0 && freq->frequency != CPUFREQ_TABLE_END) { - if (freq->frequency == CPUFREQ_ENTRY_INVALID) - continue; - - dvfs = &s3c64xx_dvfs_table[freq->index]; - found = 0; - - for (i = 0; i < count; i++) { - v = regulator_list_voltage(vddarm, i); - if (v >= dvfs->vddarm_min && v <= dvfs->vddarm_max) - found = 1; - } - - if (!found) { - pr_debug("cpufreq: %dkHz unsupported by regulator\n", - freq->frequency); - freq->frequency = CPUFREQ_ENTRY_INVALID; - } - - freq++; - } - - /* Guess based on having to do an I2C/SPI write; in future we - * will be able to query the regulator performance here. */ - regulator_latency = 1 * 1000 * 1000; -} -#endif - -static int s3c64xx_cpufreq_driver_init(struct cpufreq_policy *policy) -{ - int ret; - struct cpufreq_frequency_table *freq; - - if (policy->cpu != 0) - return -EINVAL; - - if (s3c64xx_freq_table == NULL) { - pr_err("cpufreq: No frequency information for this CPU\n"); - return -ENODEV; - } - - armclk = clk_get(NULL, "armclk"); - if (IS_ERR(armclk)) { - pr_err("cpufreq: Unable to obtain ARMCLK: %ld\n", - PTR_ERR(armclk)); - return PTR_ERR(armclk); - } - -#ifdef CONFIG_REGULATOR - vddarm = regulator_get(NULL, "vddarm"); - if (IS_ERR(vddarm)) { - ret = PTR_ERR(vddarm); - pr_err("cpufreq: Failed to obtain VDDARM: %d\n", ret); - pr_err("cpufreq: Only frequency scaling available\n"); - vddarm = NULL; - } else { - s3c64xx_cpufreq_config_regulator(); - } -#endif - - freq = s3c64xx_freq_table; - while (freq->frequency != CPUFREQ_TABLE_END) { - unsigned long r; - - /* Check for frequencies we can generate */ - r = clk_round_rate(armclk, freq->frequency * 1000); - r /= 1000; - if (r != freq->frequency) { - pr_debug("cpufreq: %dkHz unsupported by clock\n", - freq->frequency); - freq->frequency = CPUFREQ_ENTRY_INVALID; - } - - /* If we have no regulator then assume startup - * frequency is the maximum we can support. */ - if (!vddarm && freq->frequency > s3c64xx_cpufreq_get_speed(0)) - freq->frequency = CPUFREQ_ENTRY_INVALID; - - freq++; - } - - policy->cur = clk_get_rate(armclk) / 1000; - - /* Datasheet says PLL stabalisation time (if we were to use - * the PLLs, which we don't currently) is ~300us worst case, - * but add some fudge. - */ - policy->cpuinfo.transition_latency = (500 * 1000) + regulator_latency; - - ret = cpufreq_frequency_table_cpuinfo(policy, s3c64xx_freq_table); - if (ret != 0) { - pr_err("cpufreq: Failed to configure frequency table: %d\n", - ret); - regulator_put(vddarm); - clk_put(armclk); - } - - return ret; -} - -static struct cpufreq_driver s3c64xx_cpufreq_driver = { - .owner = THIS_MODULE, - .flags = 0, - .verify = s3c64xx_cpufreq_verify_speed, - .target = s3c64xx_cpufreq_set_target, - .get = s3c64xx_cpufreq_get_speed, - .init = s3c64xx_cpufreq_driver_init, - .name = "s3c", -}; - -static int __init s3c64xx_cpufreq_init(void) -{ - return cpufreq_register_driver(&s3c64xx_cpufreq_driver); -} -module_init(s3c64xx_cpufreq_init); -- cgit v0.10.2 From 4911ca1031c2ade225fdf22cc872bc121c2c2ec5 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Mon, 6 Jun 2011 18:59:02 -0700 Subject: [CPUFREQ] s5pv210-cpufreq.c: Add missing clk_put The successive calls to clk_get each call clk_put in the case of failure, but this is not done for subsequent error handling code. The calls to clk_get are moved to the end of the function, and appropriate gotos are added. A simplified version of the semantic match that finds this problem is as follows: (http://coccinelle.lip6.fr/) // @r exists@ expression e1,e2; statement S; @@ e1 = clk_get@p1(...); ... when != e1 = e2 when != clk_put(e1) when any if (...) { ... when != clk_put(e1) when != if (...) { ... clk_put(e1) ... } * return@p3 ...; } else S // Signed-off-by: Julia Lawall Signed-off-by: Kukjin Kim Signed-off-by: Dave Jones diff --git a/drivers/cpufreq/s5pv210-cpufreq.c b/drivers/cpufreq/s5pv210-cpufreq.c index ea35d3f..a7cb338 100644 --- a/drivers/cpufreq/s5pv210-cpufreq.c +++ b/drivers/cpufreq/s5pv210-cpufreq.c @@ -415,6 +415,7 @@ static int check_mem_type(void __iomem *dmc_reg) static int __init s5pv210_cpu_init(struct cpufreq_policy *policy) { unsigned long mem_type; + int ret; cpu_clk = clk_get(NULL, "armclk"); if (IS_ERR(cpu_clk)) @@ -422,19 +423,20 @@ static int __init s5pv210_cpu_init(struct cpufreq_policy *policy) dmc0_clk = clk_get(NULL, "sclk_dmc0"); if (IS_ERR(dmc0_clk)) { - clk_put(cpu_clk); - return PTR_ERR(dmc0_clk); + ret = PTR_ERR(dmc0_clk); + goto out_dmc0; } dmc1_clk = clk_get(NULL, "hclk_msys"); if (IS_ERR(dmc1_clk)) { - clk_put(dmc0_clk); - clk_put(cpu_clk); - return PTR_ERR(dmc1_clk); + ret = PTR_ERR(dmc1_clk); + goto out_dmc1; } - if (policy->cpu != 0) - return -EINVAL; + if (policy->cpu != 0) { + ret = -EINVAL; + goto out_dmc1; + } /* * check_mem_type : This driver only support LPDDR & LPDDR2. @@ -444,7 +446,8 @@ static int __init s5pv210_cpu_init(struct cpufreq_policy *policy) if ((mem_type != LPDDR) && (mem_type != LPDDR2)) { printk(KERN_ERR "CPUFreq doesn't support this memory type\n"); - return -EINVAL; + ret = -EINVAL; + goto out_dmc1; } /* Find current refresh counter and frequency each DMC */ @@ -461,6 +464,12 @@ static int __init s5pv210_cpu_init(struct cpufreq_policy *policy) policy->cpuinfo.transition_latency = 40000; return cpufreq_frequency_table_cpuinfo(policy, s5pv210_freq_table); + +out_dmc1: + clk_put(dmc0_clk); +out_dmc0: + clk_put(cpu_clk); + return ret; } static struct cpufreq_driver s5pv210_driver = { -- cgit v0.10.2 From c6e2d68558b612fdfdb0d7ddcb51ad4578b81eba Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 Jun 2011 14:49:15 +0100 Subject: [CPUFREQ] S3C6410: Support 800MHz operation in cpufreq At least some newer S3C6410 silicon supports operation up to 800MHz rather than just 667MHz. Unfortunately I don't have access to any of documentation of this other than some running systems, add a new cpufreq table entry for this based on the behaviour of those systems. Signed-off-by: Mark Brown Acked-by: Kukjin Kim Signed-off-by: Dave Jones diff --git a/drivers/cpufreq/s3c64xx-cpufreq.c b/drivers/cpufreq/s3c64xx-cpufreq.c index fc3f180..fc69178 100644 --- a/drivers/cpufreq/s3c64xx-cpufreq.c +++ b/drivers/cpufreq/s3c64xx-cpufreq.c @@ -31,6 +31,7 @@ static struct s3c64xx_dvfs s3c64xx_dvfs_table[] = { [1] = { 1050000, 1150000 }, [2] = { 1100000, 1150000 }, [3] = { 1200000, 1350000 }, + [4] = { 1300000, 1350000 }, }; static struct cpufreq_frequency_table s3c64xx_freq_table[] = { @@ -43,6 +44,7 @@ static struct cpufreq_frequency_table s3c64xx_freq_table[] = { { 2, 532000 }, { 2, 533000 }, { 3, 667000 }, + { 4, 800000 }, { 0, CPUFREQ_TABLE_END }, }; #endif -- cgit v0.10.2 From fb3b1fefaaccdbdc716db0963ba41fb6b4221e60 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 22 Jun 2011 15:08:56 +0100 Subject: [CPUFREQ] S3C64xx: Notify transition complete as soon as frequency changed The CPUFREQ_POSTCHANGE notification is used to update things that depend on the system clock rates. Since this may include the interfaces used to talk to the regulators do the notification before we try to update regulators to reflect lowered system clock rate. The voltage scaling is just a power optimisation and may not happen at all so there's no concern about it not having completed. Signed-off-by: Mark Brown Acked-by: Kukjin Kim Signed-off-by: Dave Jones diff --git a/drivers/cpufreq/s3c64xx-cpufreq.c b/drivers/cpufreq/s3c64xx-cpufreq.c index fc69178..e818248 100644 --- a/drivers/cpufreq/s3c64xx-cpufreq.c +++ b/drivers/cpufreq/s3c64xx-cpufreq.c @@ -112,6 +112,8 @@ static int s3c64xx_cpufreq_set_target(struct cpufreq_policy *policy, goto err; } + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + #ifdef CONFIG_REGULATOR if (vddarm && freqs.new < freqs.old) { ret = regulator_set_voltage(vddarm, @@ -125,8 +127,6 @@ static int s3c64xx_cpufreq_set_target(struct cpufreq_policy *policy, } #endif - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - pr_debug("cpufreq: Set actual frequency %lukHz\n", clk_get_rate(armclk) / 1000); -- cgit v0.10.2 From 90d5d0a119bcf189e8b33f776b4f8371b789b311 Mon Sep 17 00:00:00 2001 From: Huisung Kang Date: Fri, 24 Jun 2011 16:04:13 +0900 Subject: [CPUFREQ] S5PV210: Add additional symantics for "relation" in cpufreq with pm Relation has an additional symantics other than standard. s5pv310_target funtion have below additional relation. - DISABLE_FURTHER_CPUFREQ : disable further access to target - ENABLE_FURTHER_CPUFRER : enable access to target Signed-off-by: Huisung Kang Signed-off-by: Jonghwan Choi Signed-off-by: Kukjin Kim Signed-off-by: Dave Jones diff --git a/drivers/cpufreq/s5pv210-cpufreq.c b/drivers/cpufreq/s5pv210-cpufreq.c index a7cb338..48a4a90 100644 --- a/drivers/cpufreq/s5pv210-cpufreq.c +++ b/drivers/cpufreq/s5pv210-cpufreq.c @@ -30,6 +30,18 @@ static struct cpufreq_freqs freqs; #define APLL_VAL_800 ((1 << 31) | (100 << 16) | (3 << 8) | 1) /* + * relation has an additional symantics other than the standard of cpufreq + * DISALBE_FURTHER_CPUFREQ: disable further access to target + * ENABLE_FURTUER_CPUFREQ: enable access to target + */ +enum cpufreq_access { + DISABLE_FURTHER_CPUFREQ = 0x10, + ENABLE_FURTHER_CPUFREQ = 0x20, +}; + +static bool no_cpufreq_access; + +/* * DRAM configurations to calculate refresh counter for changing * frequency of memory. */ @@ -146,6 +158,22 @@ static int s5pv210_target(struct cpufreq_policy *policy, unsigned int pll_changing = 0; unsigned int bus_speed_changing = 0; + if (relation & ENABLE_FURTHER_CPUFREQ) + no_cpufreq_access = false; + + if (no_cpufreq_access) { +#ifdef CONFIG_PM_VERBOSE + pr_err("%s:%d denied access to %s as it is disabled" + "temporarily\n", __FILE__, __LINE__, __func__); +#endif + return -EINVAL; + } + + if (relation & DISABLE_FURTHER_CPUFREQ) + no_cpufreq_access = true; + + relation &= ~(ENABLE_FURTHER_CPUFREQ | DISABLE_FURTHER_CPUFREQ); + freqs.old = s5pv210_getspeed(0); if (cpufreq_frequency_table_target(policy, s5pv210_freq_table, -- cgit v0.10.2 From e8b4c1986efbb5b1e1bab9f359c340816e4d9869 Mon Sep 17 00:00:00 2001 From: Jonghwan Choi Date: Fri, 24 Jun 2011 16:04:14 +0900 Subject: [CPUFREQ] S5PV210: Add arm/int voltage control support Signed-off-by: Jonghwan Choi Signed-off-by: Kukjin Kim Signed-off-by: Dave Jones diff --git a/drivers/cpufreq/s5pv210-cpufreq.c b/drivers/cpufreq/s5pv210-cpufreq.c index 48a4a90..9a5b284 100644 --- a/drivers/cpufreq/s5pv210-cpufreq.c +++ b/drivers/cpufreq/s5pv210-cpufreq.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -77,6 +78,40 @@ static struct cpufreq_frequency_table s5pv210_freq_table[] = { {0, CPUFREQ_TABLE_END}, }; +static struct regulator *arm_regulator; +static struct regulator *int_regulator; + +struct s5pv210_dvs_conf { + int arm_volt; /* uV */ + int int_volt; /* uV */ +}; + +static const int arm_volt_max = 1350000; +static const int int_volt_max = 1250000; + +static struct s5pv210_dvs_conf dvs_conf[] = { + [L0] = { + .arm_volt = 1250000, + .int_volt = 1100000, + }, + [L1] = { + .arm_volt = 1200000, + .int_volt = 1100000, + }, + [L2] = { + .arm_volt = 1050000, + .int_volt = 1100000, + }, + [L3] = { + .arm_volt = 950000, + .int_volt = 1100000, + }, + [L4] = { + .arm_volt = 950000, + .int_volt = 1000000, + }, +}; + static u32 clkdiv_val[5][11] = { /* * Clock divider value for following @@ -157,6 +192,8 @@ static int s5pv210_target(struct cpufreq_policy *policy, unsigned int index, priv_index; unsigned int pll_changing = 0; unsigned int bus_speed_changing = 0; + int arm_volt, int_volt; + int ret = 0; if (relation & ENABLE_FURTHER_CPUFREQ) no_cpufreq_access = false; @@ -191,12 +228,23 @@ static int s5pv210_target(struct cpufreq_policy *policy, freqs.old, relation, &priv_index)) return -EINVAL; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + arm_volt = dvs_conf[index].arm_volt; + int_volt = dvs_conf[index].int_volt; if (freqs.new > freqs.old) { - /* Voltage up: will be implemented */ + ret = regulator_set_voltage(arm_regulator, + arm_volt, arm_volt_max); + if (ret) + return ret; + + ret = regulator_set_voltage(int_regulator, + int_volt, int_volt_max); + if (ret) + return ret; } + cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + /* Check if there need to change PLL */ if ((index == L0) || (priv_index == L0)) pll_changing = 1; @@ -408,7 +456,11 @@ static int s5pv210_target(struct cpufreq_policy *policy, } if (freqs.new < freqs.old) { - /* Voltage down: will be implemented */ + regulator_set_voltage(int_regulator, + int_volt, int_volt_max); + + regulator_set_voltage(arm_regulator, + arm_volt, arm_volt_max); } cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); @@ -515,6 +567,19 @@ static struct cpufreq_driver s5pv210_driver = { static int __init s5pv210_cpufreq_init(void) { + arm_regulator = regulator_get(NULL, "vddarm"); + if (IS_ERR(arm_regulator)) { + pr_err("failed to get regulator vddarm"); + return PTR_ERR(arm_regulator); + } + + int_regulator = regulator_get(NULL, "vddint"); + if (IS_ERR(int_regulator)) { + pr_err("failed to get regulator vddint"); + regulator_put(arm_regulator); + return PTR_ERR(int_regulator); + } + return cpufreq_register_driver(&s5pv210_driver); } -- cgit v0.10.2 From 405e6d6df739a27a267b381213aa9e86c4929543 Mon Sep 17 00:00:00 2001 From: Huisung Kang Date: Fri, 24 Jun 2011 16:04:15 +0900 Subject: [CPUFREQ] S5PV210: Add pm_notifier to prevent system unstable Minimum 800MHz is needed to enter/exit suspend mode due to voltage mismatch. Signed-off-by: Huisung Kang Signed-off-by: Jonghwan Choi Signed-off-by: Kukjin Kim Signed-off-by: Dave Jones diff --git a/drivers/cpufreq/s5pv210-cpufreq.c b/drivers/cpufreq/s5pv210-cpufreq.c index 9a5b284..79b1b4e 100644 --- a/drivers/cpufreq/s5pv210-cpufreq.c +++ b/drivers/cpufreq/s5pv210-cpufreq.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,9 @@ static struct cpufreq_freqs freqs; #define APLL_VAL_1000 ((1 << 31) | (125 << 16) | (3 << 8) | 1) #define APLL_VAL_800 ((1 << 31) | (100 << 16) | (3 << 8) | 1) +/* Use 800MHz when entering sleep mode */ +#define SLEEP_FREQ (800 * 1000) + /* * relation has an additional symantics other than the standard of cpufreq * DISALBE_FURTHER_CPUFREQ: disable further access to target @@ -552,6 +556,30 @@ out_dmc0: return ret; } +static int s5pv210_cpufreq_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + int ret; + + switch (event) { + case PM_SUSPEND_PREPARE: + ret = cpufreq_driver_target(cpufreq_cpu_get(0), SLEEP_FREQ, + DISABLE_FURTHER_CPUFREQ); + if (ret < 0) + return NOTIFY_BAD; + + return NOTIFY_OK; + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + cpufreq_driver_target(cpufreq_cpu_get(0), SLEEP_FREQ, + ENABLE_FURTHER_CPUFREQ); + + return NOTIFY_OK; + } + + return NOTIFY_DONE; +} + static struct cpufreq_driver s5pv210_driver = { .flags = CPUFREQ_STICKY, .verify = s5pv210_verify_speed, @@ -565,6 +593,10 @@ static struct cpufreq_driver s5pv210_driver = { #endif }; +static struct notifier_block s5pv210_cpufreq_notifier = { + .notifier_call = s5pv210_cpufreq_notifier_event, +}; + static int __init s5pv210_cpufreq_init(void) { arm_regulator = regulator_get(NULL, "vddarm"); @@ -580,6 +612,8 @@ static int __init s5pv210_cpufreq_init(void) return PTR_ERR(int_regulator); } + register_pm_notifier(&s5pv210_cpufreq_notifier); + return cpufreq_register_driver(&s5pv210_driver); } -- cgit v0.10.2 From 5b02b7794b555e299c5e9298c6b223b538888ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arve=20Hj=C3=B8nnev=C3=A5g?= Date: Fri, 24 Jun 2011 16:04:16 +0900 Subject: [CPUFREQ] S5PV210: Lock a mutex while changing the cpu frequency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without this lock the call to change the frequency for suspend could switch to a new frequency while another thread was still changing the cpu voltage. Signed-off-by: Arve Hjønnevåg Signed-off-by: Jonghwan Choi Signed-off-by: Kukjin Kim Signed-off-by: Dave Jones diff --git a/drivers/cpufreq/s5pv210-cpufreq.c b/drivers/cpufreq/s5pv210-cpufreq.c index 79b1b4e..7fba356 100644 --- a/drivers/cpufreq/s5pv210-cpufreq.c +++ b/drivers/cpufreq/s5pv210-cpufreq.c @@ -26,6 +26,7 @@ static struct clk *cpu_clk; static struct clk *dmc0_clk; static struct clk *dmc1_clk; static struct cpufreq_freqs freqs; +static DEFINE_MUTEX(set_freq_lock); /* APLL M,P,S values for 1G/800Mhz */ #define APLL_VAL_1000 ((1 << 31) | (125 << 16) | (3 << 8) | 1) @@ -199,6 +200,8 @@ static int s5pv210_target(struct cpufreq_policy *policy, int arm_volt, int_volt; int ret = 0; + mutex_lock(&set_freq_lock); + if (relation & ENABLE_FURTHER_CPUFREQ) no_cpufreq_access = false; @@ -207,7 +210,8 @@ static int s5pv210_target(struct cpufreq_policy *policy, pr_err("%s:%d denied access to %s as it is disabled" "temporarily\n", __FILE__, __LINE__, __func__); #endif - return -EINVAL; + ret = -EINVAL; + goto exit; } if (relation & DISABLE_FURTHER_CPUFREQ) @@ -218,19 +222,23 @@ static int s5pv210_target(struct cpufreq_policy *policy, freqs.old = s5pv210_getspeed(0); if (cpufreq_frequency_table_target(policy, s5pv210_freq_table, - target_freq, relation, &index)) - return -EINVAL; + target_freq, relation, &index)) { + ret = -EINVAL; + goto exit; + } freqs.new = s5pv210_freq_table[index].frequency; freqs.cpu = 0; if (freqs.new == freqs.old) - return 0; + goto exit; /* Finding current running level index */ if (cpufreq_frequency_table_target(policy, s5pv210_freq_table, - freqs.old, relation, &priv_index)) - return -EINVAL; + freqs.old, relation, &priv_index)) { + ret = -EINVAL; + goto exit; + } arm_volt = dvs_conf[index].arm_volt; int_volt = dvs_conf[index].int_volt; @@ -239,12 +247,12 @@ static int s5pv210_target(struct cpufreq_policy *policy, ret = regulator_set_voltage(arm_regulator, arm_volt, arm_volt_max); if (ret) - return ret; + goto exit; ret = regulator_set_voltage(int_regulator, int_volt, int_volt_max); if (ret) - return ret; + goto exit; } cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); @@ -471,7 +479,9 @@ static int s5pv210_target(struct cpufreq_policy *policy, printk(KERN_DEBUG "Perf changed[L%d]\n", index); - return 0; +exit: + mutex_unlock(&set_freq_lock); + return ret; } #ifdef CONFIG_PM -- cgit v0.10.2 From 74df8e69e901e624fe0b439f914aa7be66201154 Mon Sep 17 00:00:00 2001 From: Todd Poynor Date: Fri, 24 Jun 2011 16:04:17 +0900 Subject: [CPUFREQ] S5PV210: Adjust udelay prior to voltage scaling down Voltage scaling accesses the MAX8998 regulators over bit-banged I2C with lots of udelays. In the case of decreasing CPU speed, the number of loops per us for udelay needs to be adjusted prior to decreasing voltage to avoid delaying for up to 10X too long. Signed-off-by: Todd Poynor Signed-off-by: Jonghwan Choi Signed-off-by: Kukjin Kim Signed-off-by: Dave Jones diff --git a/drivers/cpufreq/s5pv210-cpufreq.c b/drivers/cpufreq/s5pv210-cpufreq.c index 7fba356..155242c 100644 --- a/drivers/cpufreq/s5pv210-cpufreq.c +++ b/drivers/cpufreq/s5pv210-cpufreq.c @@ -467,6 +467,8 @@ static int s5pv210_target(struct cpufreq_policy *policy, } } + cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + if (freqs.new < freqs.old) { regulator_set_voltage(int_regulator, int_volt, int_volt_max); @@ -475,8 +477,6 @@ static int s5pv210_target(struct cpufreq_policy *policy, arm_volt, arm_volt_max); } - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - printk(KERN_DEBUG "Perf changed[L%d]\n", index); exit: -- cgit v0.10.2 From fe7f1bcbff917f22e121d4b2c045fb1de80cc450 Mon Sep 17 00:00:00 2001 From: Huisung Kang Date: Fri, 24 Jun 2011 16:04:18 +0900 Subject: [CPUFREQ] S5PV210: Add reboot notifier to prevent system hang When system reboot, the CPUFREQ level should be 800MHz to prevent system lockup. Signed-off-by: Huisung Kang Signed-off-by: Jonghwan Choi Signed-off-by: Kukjin Kim Signed-off-by: Dave Jones diff --git a/drivers/cpufreq/s5pv210-cpufreq.c b/drivers/cpufreq/s5pv210-cpufreq.c index 155242c..7c4bb070 100644 --- a/drivers/cpufreq/s5pv210-cpufreq.c +++ b/drivers/cpufreq/s5pv210-cpufreq.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -590,6 +591,19 @@ static int s5pv210_cpufreq_notifier_event(struct notifier_block *this, return NOTIFY_DONE; } +static int s5pv210_cpufreq_reboot_notifier_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + int ret; + + ret = cpufreq_driver_target(cpufreq_cpu_get(0), SLEEP_FREQ, + DISABLE_FURTHER_CPUFREQ); + if (ret < 0) + return NOTIFY_BAD; + + return NOTIFY_DONE; +} + static struct cpufreq_driver s5pv210_driver = { .flags = CPUFREQ_STICKY, .verify = s5pv210_verify_speed, @@ -607,6 +621,10 @@ static struct notifier_block s5pv210_cpufreq_notifier = { .notifier_call = s5pv210_cpufreq_notifier_event, }; +static struct notifier_block s5pv210_cpufreq_reboot_notifier = { + .notifier_call = s5pv210_cpufreq_reboot_notifier_event, +}; + static int __init s5pv210_cpufreq_init(void) { arm_regulator = regulator_get(NULL, "vddarm"); @@ -623,6 +641,7 @@ static int __init s5pv210_cpufreq_init(void) } register_pm_notifier(&s5pv210_cpufreq_notifier); + register_reboot_notifier(&s5pv210_cpufreq_reboot_notifier); return cpufreq_register_driver(&s5pv210_driver); } -- cgit v0.10.2 From ef993ef8dcd4f3e4d058400c5bd2f7c547344e74 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 28 Jun 2011 20:26:49 -0700 Subject: [CPUFREQ] S3C6410: Add some lower frequencies for 800MHz base clock operation By extension from the 667MHz based clocks currently supported add 100MHz and 200MHz operating points. Due to a lack of documentation these have not been confirmed as supported but by extension from the existing frequencies they should be OK, and I've given them quite a bit of runtime testing. The major risk is synchronization with the non-ARM clocks but as we can't currently scale the ARM PLL the risk should be relatively low. Signed-off-by: Mark Brown Acked-by: Kukjin Kim Signed-off-by: Dave Jones diff --git a/drivers/cpufreq/s3c64xx-cpufreq.c b/drivers/cpufreq/s3c64xx-cpufreq.c index e818248..b8d1d20 100644 --- a/drivers/cpufreq/s3c64xx-cpufreq.c +++ b/drivers/cpufreq/s3c64xx-cpufreq.c @@ -36,7 +36,9 @@ static struct s3c64xx_dvfs s3c64xx_dvfs_table[] = { static struct cpufreq_frequency_table s3c64xx_freq_table[] = { { 0, 66000 }, + { 0, 100000 }, { 0, 133000 }, + { 1, 200000 }, { 1, 222000 }, { 1, 266000 }, { 2, 333000 }, -- cgit v0.10.2 From 2f0d6f20ec4cc157ba092e008ed20a99590142c4 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Fri, 8 Jul 2011 14:20:44 +0800 Subject: [CPUFREQ] exynos4210: make needlessly global symbols static The following symbols are needlessly defined global: exynos4_verify_speed exynos4_getspeed exynos4_set_clkdiv Make them static. Signed-off-by: Axel Lin Acked-by: Kukjin Kim Signed-off-by: Dave Jones diff --git a/drivers/cpufreq/exynos4210-cpufreq.c b/drivers/cpufreq/exynos4210-cpufreq.c index 54025fc..b7c3a84 100644 --- a/drivers/cpufreq/exynos4210-cpufreq.c +++ b/drivers/cpufreq/exynos4210-cpufreq.c @@ -191,17 +191,17 @@ static unsigned int exynos4_apll_pms_table[CPUFREQ_LEVEL_END] = { ((200 << 16) | (6 << 8) | 4), }; -int exynos4_verify_speed(struct cpufreq_policy *policy) +static int exynos4_verify_speed(struct cpufreq_policy *policy) { return cpufreq_frequency_table_verify(policy, exynos4_freq_table); } -unsigned int exynos4_getspeed(unsigned int cpu) +static unsigned int exynos4_getspeed(unsigned int cpu) { return clk_get_rate(cpu_clk) / 1000; } -void exynos4_set_clkdiv(unsigned int div_index) +static void exynos4_set_clkdiv(unsigned int div_index) { unsigned int tmp; -- cgit v0.10.2 From 133de1211982bd2ba9ab401f7a73d25d052ccd61 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Fri, 8 Jul 2011 14:24:36 +0800 Subject: [CPUFREQ] s5pv210: make needlessly global symbols static The following symbols are needlessly defined global: s5pv210_verify_speed s5pv210_getspeed Make them static. Signed-off-by: Axel Lin Acked-by: Kukjin Kim Signed-off-by: Dave Jones diff --git a/drivers/cpufreq/s5pv210-cpufreq.c b/drivers/cpufreq/s5pv210-cpufreq.c index 7c4bb070..a484aae 100644 --- a/drivers/cpufreq/s5pv210-cpufreq.c +++ b/drivers/cpufreq/s5pv210-cpufreq.c @@ -174,7 +174,7 @@ static void s5pv210_set_refresh(enum s5pv210_dmc_port ch, unsigned long freq) __raw_writel(tmp1, reg); } -int s5pv210_verify_speed(struct cpufreq_policy *policy) +static int s5pv210_verify_speed(struct cpufreq_policy *policy) { if (policy->cpu) return -EINVAL; @@ -182,7 +182,7 @@ int s5pv210_verify_speed(struct cpufreq_policy *policy) return cpufreq_frequency_table_verify(policy, s5pv210_freq_table); } -unsigned int s5pv210_getspeed(unsigned int cpu) +static unsigned int s5pv210_getspeed(unsigned int cpu) { if (cpu) return 0; -- cgit v0.10.2