[PATCH v2 5/5] ARM: S5PV210: Initial CPUFREQ Support
Maurus Cuelenaere
mcuelenaere at gmail.com
Fri Jul 16 07:02:43 EDT 2010
Op 16-07-10 10:01, MyungJoo Ham schreef:
> S5PV210 CPUFREQ Support.
>
> This CPUFREQ may work without PMIC's DVS support. However, it is not
> as effective without DVS support as supposed. AVS is not supported in
> this version.
>
> Note that CLK_SRC of some clocks including ARMCLK, G3D, G2D, MFC,
> and ONEDRAM are modified directly without updating clksrc_src
> representations of the clocks. Because CPUFREQ reverts the CLK_SRC
> to the supposed values, this does not affect the consistency as long as
> there are no other modules that modifies clock sources of ARMCLK, G3D,
> G2D, MFC, and ONEDRAM (and only CPUFREQ should have the control). As
> soon as clock framework is settled, we may update source clocks
> (.parent field) of those clocks accordingly later.
>
> --
> v2:
> - Ramp-up delay is removed. (let regulator framework do the job)
> - Provide proper max values for regulator_set_voltage
> - Removed unneccesary #ifdef's.
> - Removed unnecessary initialiser for CLK_OUT
>
> Signed-off-by: MyungJoo Ham <myungjoo.ham at samsung.com>
> Signed-off-by: Kyungmin Park <kyungmin.park at samsung.com>
> ---
> arch/arm/Kconfig | 1 +
> arch/arm/mach-s5pv210/Makefile | 3 +
> arch/arm/mach-s5pv210/cpufreq-s5pv210.c | 776 +++++++++++++++++++++++++
> arch/arm/mach-s5pv210/include/mach/cpu-freq.h | 50 ++
> 4 files changed, 830 insertions(+), 0 deletions(-)
> create mode 100644 arch/arm/mach-s5pv210/cpufreq-s5pv210.c
> create mode 100644 arch/arm/mach-s5pv210/include/mach/cpu-freq.h
>
> diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
> index 98922f7..d5a5916 100644
> --- a/arch/arm/Kconfig
> +++ b/arch/arm/Kconfig
> @@ -701,6 +701,7 @@ config ARCH_S5PV210
> select HAVE_CLK
> select ARM_L1_CACHE_SHIFT_6
> select ARCH_USES_GETTIMEOFFSET
> + select ARCH_HAS_CPUFREQ
> help
> Samsung S5PV210/S5PC110 series based systems
>
> diff --git a/arch/arm/mach-s5pv210/Makefile b/arch/arm/mach-s5pv210/Makefile
> index aae592a..293dbac 100644
> --- a/arch/arm/mach-s5pv210/Makefile
> +++ b/arch/arm/mach-s5pv210/Makefile
> @@ -34,3 +34,6 @@ obj-$(CONFIG_S5PV210_SETUP_I2C2) += setup-i2c2.o
> obj-$(CONFIG_S5PV210_SETUP_KEYPAD) += setup-keypad.o
> obj-$(CONFIG_S5PV210_SETUP_SDHCI) += setup-sdhci.o
> obj-$(CONFIG_S5PV210_SETUP_SDHCI_GPIO) += setup-sdhci-gpio.o
> +
> +# CPUFREQ (DVFS)
> +obj-$(CONFIG_CPU_FREQ) += cpufreq-s5pv210.o
> diff --git a/arch/arm/mach-s5pv210/cpufreq-s5pv210.c b/arch/arm/mach-s5pv210/cpufreq-s5pv210.c
> new file mode 100644
> index 0000000..82ec31b
> --- /dev/null
> +++ b/arch/arm/mach-s5pv210/cpufreq-s5pv210.c
> @@ -0,0 +1,776 @@
> +/* linux/arch/arm/plat-s5pc11x/s5pc11x-cpufreq.c
> + *
> + * Copyright (C) 2010 Samsung Electronics Co., Ltd.
> + *
> + * CPU frequency scaling for S5PC110
> + * Based on cpu-sa1110.c
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +#include <linux/types.h>
> +#include <linux/kernel.h>
> +#include <linux/sched.h>
> +#include <linux/delay.h>
> +#include <linux/init.h>
> +#include <linux/err.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/gpio.h>
> +#include <asm/system.h>
> +
> +#include <mach/hardware.h>
> +#include <mach/map.h>
> +#include <mach/regs-clock.h>
> +#include <mach/regs-gpio.h>
> +#include <mach/cpu-freq.h>
> +
> +#include <plat/cpu-freq.h>
> +#include <plat/pll.h>
> +#include <plat/clock.h>
> +#include <plat/gpio-cfg.h>
> +#include <plat/regs-fb.h>
> +#ifdef CONFIG_PM
> +#include <plat/pm.h>
> +#endif
> +
> +static struct clk *mpu_clk;
> +static struct regulator *arm_regulator;
> +static struct regulator *internal_regulator;
> +
> +struct s3c_cpufreq_freqs s3c_freqs;
> +
> +#ifdef CONFIG_CPU_S5PC110_EVT0_ERRATA
> +#define CPUFREQ_DISABLE_1GHZ
> +#endif
> +/* #define CPUFREQ_DISABLE_100MHZ */
> +
> +static unsigned long previous_arm_volt;
> +
> +/* frequency */
> +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},
> +};
> +
> +struct s5pc11x_dvs_conf {
> + const unsigned long lvl; /* DVFS level : L0,L1,L2,L3... */
Why add this when you don't use it? You could use the level as the lookup key instead.
> + unsigned long arm_volt; /* uV */
> + unsigned long int_volt; /* uV */
> +};
> +
> +#ifdef CONFIG_CPU_S5PC110_EVT0_ERRATA
> +const unsigned long arm_volt_max = 1350000;
> +const unsigned long int_volt_max = 1250000;
> +#else
> +const unsigned long arm_volt_max = 1300000;
> +const unsigned long int_volt_max = 1200000;
> +#endif
> +
> +static struct s5pc11x_dvs_conf s5pv210_dvs_conf[] = {
> +#ifdef CONFIG_CPU_S5PC110_EVT0_ERRATA
> + {
> + .lvl = L0,
> + .arm_volt = 1300000,
> + .int_volt = 1200000,
> + }, {
> + .lvl = L1,
> + .arm_volt = 1250000,
> + .int_volt = 1200000,
> +
> + }, {
> + .lvl = L2,
> + .arm_volt = 1250000,
> + .int_volt = 1200000,
> +
> + }, {
> + .lvl = L3,
> + .arm_volt = 1250000,
> + .int_volt = 1200000,
> + }, {
> + .lvl = L4,
> + .arm_volt = 1250000,
> + .int_volt = 1200000,
> + },
> +#else
> + {
> + .lvl = L0,
> + .arm_volt = 1250000,
> + .int_volt = 1100000,
> + }, {
> + .lvl = L1,
> + .arm_volt = 1200000,
> + .int_volt = 1100000,
> +
> + }, {
> + .lvl = L2,
> + .arm_volt = 1050000,
> + .int_volt = 1100000,
> +
> + }, {
> + .lvl = L3,
> + .arm_volt = 950000,
> + .int_volt = 1100000,
> + }, {
> + .lvl = L4,
> + .arm_volt = 950000,
> + .int_volt = 1000000,
> + },
> +#endif
> +};
> +
> +static u32 clkdiv_val[5][11] = {
> + /*{ APLL, A2M, HCLK_MSYS, PCLK_MSYS,
> + * HCLK_DSYS, PCLK_DSYS, HCLK_PSYS, PCLK_PSYS, ONEDRAM,
> + * MFC, G3D }
> + */
> + /* L0 : [1000/200/200/100][166/83][133/66][200/200] */
> + {0, 4, 4, 1, 3, 1, 4, 1, 3, 0, 0},
> + /* L1 : [800/200/200/100][166/83][133/66][200/200] */
> + {0, 3, 3, 1, 3, 1, 4, 1, 3, 0, 0},
> + /* L2 : [400/200/200/100][166/83][133/66][200/200] */
> + {1, 3, 1, 1, 3, 1, 4, 1, 3, 0, 0},
> + /* L3 : [200/200/200/100][166/83][133/66][200/200] */
> + {3, 3, 0, 1, 3, 1, 4, 1, 3, 0, 0},
> + /* L4 : [100/100/100/100][83/83][66/66][100/100] */
> + {7, 7, 0, 0, 7, 0, 9, 0, 7, 0, 0},
> +};
> +
> +static struct s3c_freq s5pv210_clk_info[] = {
> + [L0] = { /* L0: 1GHz */
> + .fclk = 1000000,
> + .armclk = 1000000,
> + .hclk_tns = 0,
> + .hclk = 133000,
> + .pclk = 66000,
> + .hclk_msys = 200000,
> + .pclk_msys = 100000,
> + .hclk_dsys = 166750,
> + .pclk_dsys = 83375,
> + },
> + [L1] = { /* L1: 800MHz */
> + .fclk = 800000,
> + .armclk = 800000,
> + .hclk_tns = 0,
> + .hclk = 133000,
> + .pclk = 66000,
> + .hclk_msys = 200000,
> + .pclk_msys = 100000,
> + .hclk_dsys = 166750,
> + .pclk_dsys = 83375,
> + },
> + [L2] = { /* L2: 400MHz */
> + .fclk = 800000,
> + .armclk = 400000,
> + .hclk_tns = 0,
> + .hclk = 133000,
> + .pclk = 66000,
> + .hclk_msys = 200000,
> + .pclk_msys = 100000,
> + .hclk_dsys = 166750,
> + .pclk_dsys = 83375,
> + },
> + [L3] = { /* L3: 200MHz */
> + .fclk = 800000,
> + .armclk = 200000,
> + .hclk_tns = 0,
> + .hclk = 133000,
> + .pclk = 66000,
> + .hclk_msys = 200000,
> + .pclk_msys = 100000,
> + .hclk_dsys = 166750,
> + .pclk_dsys = 83375,
> + },
> + [L4] = { /* L4: 100MHz */
> + .fclk = 800000,
> + .armclk = 100000,
> + .hclk_tns = 0,
> + .hclk = 66000,
> + .pclk = 66000,
> + .hclk_msys = 100000,
> + .pclk_msys = 100000,
> + .hclk_dsys = 83375,
> + .pclk_dsys = 83375,
> + },
> +};
> +
> +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)
> +{
> + unsigned long rate;
> +
> + if (cpu)
> + return 0;
> +
> + rate = clk_get_rate(mpu_clk) / KHZ_T;
> +
> + return rate;
> +}
> +
> +static void s5pv210_target_APLL2MPLL(unsigned int index,
> + unsigned int bus_speed_changing)
> +{
> + unsigned int reg;
> +
> + /* 1. Temporarily 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 |= (0x3 << S5P_CLKDIV2_G3D_SHIFT) |
> + (0x3 << S5P_CLKDIV2_MFC_SHIFT);
> +#ifndef CONFIG_CPU_S5PC110_EVT0_ERRATA
> + reg &= ~S5P_CLKDIV2_G2D_MASK;
> + reg |= (0x2 << S5P_CLKDIV2_G2D_SHIFT);
> +#endif
> + __raw_writel(reg, S5P_CLK_DIV2);
> +
> + /* For MFC, G3D dividing */
> + do {
> + reg = __raw_readl(S5P_CLK_DIV_STAT0);
> + } while (reg & ((1 << 16) | (1 << 17)));
> +
> + /* 2. Change SCLKA2M(200MHz) to SCLKMPLL in MFC_MUX, G3D MUX
> + * (100/4=50)->(667/4=166)MHz
> + **/
> + reg = __raw_readl(S5P_CLK_SRC2);
> + reg &= ~(S5P_CLKSRC2_G3D_MASK | S5P_CLKSRC2_MFC_MASK);
> + reg |= (0x1 << S5P_CLKSRC2_G3D_SHIFT) |
> + (0x1 << S5P_CLKSRC2_MFC_SHIFT);
> +#ifndef CONFIG_CPU_S5PC110_EVT0_ERRATA
> + reg &= ~S5P_CLKSRC2_G2D_MASK;
> + reg |= (0x1 << S5P_CLKSRC2_G2D_SHIFT);
> +#endif
> + __raw_writel(reg, S5P_CLK_SRC2);
> +
> + do {
> + reg = __raw_readl(S5P_CLK_MUX_STAT1);
> + } while (reg & ((1 << 7) | (1 << 3)));
> +
> + /* 3. DMC1 refresh count for 133MHz if (index == L4) is true
> + * refresh counter is already programmed in the outer/upper
> + * code. 0x287 at 83MHz
> + **/
> + if (!bus_speed_changing)
> + __raw_writel(0x40d, S5P_VA_DMC1 + 0x30);
> +
> + /* 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_CLK_MUX_STAT0);
> + } while (reg & (0x1 << 18));
> +
> +#if defined(CONFIG_S5PC110_H_TYPE)
> + /* DMC0 source clock: SCLKA2M -> SCLKMPLL */
> + __raw_writel(0x50e, S5P_VA_DMC0 + 0x30);
> +
> + reg = __raw_readl(S5P_CLK_DIV6);
> + reg &= ~(S5P_CLKDIV6_ONEDRAM_MASK);
> + reg |= (0x3 << S5P_CLKDIV6_ONEDRAM_SHIFT);
> + __raw_writel(reg, S5P_CLK_DIV6);
> +
> + do {
> + reg = __raw_readl(S5P_CLK_DIV_STAT1);
> + } while (reg & (1 << 15));
> +
> + reg = __raw_readl(S5P_CLK_SRC6);
> + reg &= ~(S5P_CLKSRC6_ONEDRAM_MASK);
> + reg |= (0x1 << S5P_CLKSRC6_ONEDRAM_SHIFT);
> + __raw_writel(reg, S5P_CLK_SRC6);
> +
> + do {
> + reg = __raw_readl(S5P_CLK_MUX_STAT1);
> + } while (reg & (1 << 11));
> +#endif
> +}
> +
> +static void s5pv210_target_MPLL2APLL(unsigned int index,
> + unsigned int bus_speed_changing)
> +{
> + unsigned int reg;
> +
> + /* 7. Set Lock time = 30us*24MHz = 02cf (EVT1) */
> +#ifdef CONFIG_CPU_S5PC110_EVT0_ERRATA
> + /* Lock time = 300us*24Mhz = 7200(0x1c20)*/
> + __raw_writel(0x1c20, S5P_APLL_LOCK);
> +#else
> + /* Lock time = 30us*24Mhz = 0x2cf*/
> + __raw_writel(0x2cf, S5P_APLL_LOCK);
> +#endif
> +
> + /* 8. Turn on APLL
> + * 8-1. Set PMS values
> + * 8-3. Wait until the PLL is locked
> + **/
> + if (index == L0)
> + /* APLL FOUT becomes 1000 Mhz */
> + __raw_writel(PLL45XX_APLL_VAL_1000, S5P_APLL_CON);
> + else
> + /* APLL FOUT becomes 800 Mhz */
> + __raw_writel(PLL45XX_APLL_VAL_800, S5P_APLL_CON);
> +
> + do {
> + reg = __raw_readl(S5P_APLL_CON);
> + } while (!(reg & (0x1 << 29)));
> +
> + /* 9. Change source 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);
> +#ifndef CONFIG_CPU_S5PC110_EVT0_ERRATA
> + reg &= ~S5P_CLKSRC2_G2D_MASK;
> + reg |= 0x1 << S5P_CLKSRC2_G2D_SHIFT;
> +#endif
> + __raw_writel(reg, S5P_CLK_SRC2);
> +
> + do {
> + reg = __raw_readl(S5P_CLK_MUX_STAT1);
> + } while (reg & ((1 << 7) | (1 << 3)));
> +
> + /* 10. 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);
> +#ifndef CONFIG_CPU_S5PC110_EVT0_ERRATA
> + reg &= ~S5P_CLKDIV2_G2D_MASK;
> + reg |= 0x2 << S5P_CLKDIV2_G2D_SHIFT;
> +#endif
> + __raw_writel(reg, S5P_CLK_DIV2);
> +
> + do {
> + reg = __raw_readl(S5P_CLK_DIV_STAT0);
> + } while (reg & ((1 << 16) | (1 << 17)));
> +
> + /* 11. Change MPLL to APLL in MSYS_MUX */
> + reg = __raw_readl(S5P_CLK_SRC0);
> + reg &= ~(S5P_CLKSRC0_MUX200_MASK);
> + reg |= (0x0 << S5P_CLKSRC0_MUX200_SHIFT); /* SCLKMPLL -> SCLKAPLL */
> + __raw_writel(reg, S5P_CLK_SRC0);
> +
> + do {
> + reg = __raw_readl(S5P_CLK_MUX_STAT0);
> + } while (reg & (0x1 << 18));
> +
> + /* 12. DMC1 refresh counter
> + * L4: DMC1 = 100MHz 7.8us/(1/100) = 0x30c
> + * Others: DMC1 = 200MHz 7.8us/(1/200) = 0x618
> + **/
> + if (!bus_speed_changing)
> + __raw_writel(0x618, S5P_VA_DMC1 + 0x30);
> +
> +#if defined(CONFIG_S5PC110_H_TYPE)
> + /* DMC0 source clock: SCLKMPLL -> SCLKA2M */
> + reg = __raw_readl(S5P_CLK_SRC6);
> + reg &= ~(S5P_CLKSRC6_ONEDRAM_MASK);
> + reg |= (0x0 << S5P_CLKSRC6_ONEDRAM_SHIFT);
> + __raw_writel(reg, S5P_CLK_SRC6);
> +
> + do {
> + reg = __raw_readl(S5P_CLK_MUX_STAT1);
> + } while (reg & (1 << 11));
> +
> + reg = __raw_readl(S5P_CLK_DIC6);
> + reg &= ~(S5P_CLKDIV6_ONEDRAM_MASK);
> + reg |= (0x0 << S5P_CLKDIV6_ONEDRAM_SHIFT);
> + __raw_writel(reg, S5P_CLK_DIV6);
> +
> + do {
> + reg = __raw_readl(S5P_CLK_DIV_STAT1);
> + } while (reg & (1 << 15));
> + __raw_writel(0x618, S5P_VA_DMC0 + 0x30);
> +#endif
> +}
> +
> +#ifdef CONFIG_PM
> +static int no_cpufreq_access;
> +/* s5pv210_target: relation has an additional symantics other than
> + * the standard
> + * [0x30]:
> + * 1: disable further access to target until being re-enabled.
> + * 2: re-enable access to target */
> +#endif
> +static int s5pv210_target(struct cpufreq_policy *policy,
> + unsigned int target_freq,
> + unsigned int relation)
> +{
> + static int initialized;
> + int ret = 0;
> + unsigned long arm_clk;
> + unsigned int index, reg, arm_volt, int_volt;
> + unsigned int pll_changing = 0;
> + unsigned int bus_speed_changing = 0;
> +
> +#ifdef CONFIG_CPU_FREQ_DEBUG
> + printk(KERN_INFO "cpufreq: Entering for %dkHz\n", target_freq);
> +#endif
cpufreq_debug_printk(CPUFREQ_DEBUG_DRIVER, <prefix>, ...)
> +#ifdef CONFIG_PM
> + if ((relation & ENABLE_FURTHER_CPUFREQ) &&
> + (relation & DISABLE_FURTHER_CPUFREQ)) {
> + /* Invalidate both if both marked */
> + relation &= ~ENABLE_FURTHER_CPUFREQ;
> + relation &= ~DISABLE_FURTHER_CPUFREQ;
> + printk(KERN_ERR "%s:%d denied marking \"FURTHER_CPUFREQ\""
> + " as both marked.\n",
> + __FILE__, __LINE__);
> + }
> + if (relation & ENABLE_FURTHER_CPUFREQ)
> + no_cpufreq_access = 0;
> + if (no_cpufreq_access == 1) {
> +#ifdef CONFIG_PM_VERBOSE
> + printk(KERN_ERR "%s:%d denied access to %s as it is disabled"
> + " temporarily\n", __FILE__, __LINE__, __func__);
Use pr_err(...)
> +#endif
> + ret = -EINVAL;
> + goto out;
> + }
> + if (relation & DISABLE_FURTHER_CPUFREQ)
> + no_cpufreq_access = 1;
> + relation &= ~MASK_FURTHER_CPUFREQ;
> +#endif
> +
> + s3c_freqs.freqs.old = s5pv210_getspeed(0);
> +
> + if (cpufreq_frequency_table_target(policy, s5pv210_freq_table,
> + target_freq, relation, &index)) {
> + ret = -EINVAL;
> + goto out;
> + }
> +#ifdef CPUFREQ_DISABLE_100MHZ
> + if (index == L4)
> + index = L3;
> +#endif
> +#ifdef CPUFREQ_DISABLE_1GHZ
> + if (index == L0)
> + index = L1;
> +#endif
> +
> + arm_clk = s5pv210_freq_table[index].frequency;
> +
> + s3c_freqs.freqs.new = arm_clk;
> + s3c_freqs.freqs.cpu = 0;
> +
> + if (s3c_freqs.freqs.new == s3c_freqs.freqs.old &&
> + initialized >= 2)
> + return 0;
> + else if (initialized < 2)
> + initialized++;
> +
> + arm_volt = s5pv210_dvs_conf[index].arm_volt;
> + int_volt = s5pv210_dvs_conf[index].int_volt;
> +
> + /* iNew clock information update */
> + memcpy(&s3c_freqs.new, &s5pv210_clk_info[index],
> + sizeof(struct s3c_freq));
> +
> + if (s3c_freqs.freqs.new >= s3c_freqs.freqs.old) {
> + /* Voltage up code: increase ARM first */
> + if (!IS_ERR_OR_NULL(arm_regulator) &&
> + !IS_ERR_OR_NULL(internal_regulator)) {
> + regulator_set_voltage(arm_regulator,
> + arm_volt, arm_volt_max);
> + regulator_set_voltage(internal_regulator,
> + int_volt, int_volt_max);
> + }
> + }
> + cpufreq_notify_transition(&s3c_freqs.freqs, CPUFREQ_PRECHANGE);
> +
> + if (s3c_freqs.new.fclk != s3c_freqs.old.fclk || initialized < 2)
> + pll_changing = 1;
> +
> + if (s3c_freqs.new.hclk_msys != s3c_freqs.old.hclk_msys ||
> + initialized < 2)
> + 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)
> + __raw_writel(0x287, S5P_VA_DMC1 + 0x30);
> + else
> + __raw_writel(0x30c, S5P_VA_DMC1 + 0x30);
> +
> + __raw_writel(0x287, S5P_VA_DMC0 + 0x30);
> + }
> +
> + /* 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)
> + s5pv210_target_APLL2MPLL(index, bus_speed_changing);
> +
> + /* ARM MCS value changed */
> + if (index != L4) {
> + reg = __raw_readl(S5P_ARM_MCS_CON);
> + reg &= ~0x3;
> + reg |= 0x1;
> + __raw_writel(reg, S5P_ARM_MCS_CON);
> + }
> +
> + 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_CLK_DIV_STAT0);
> + } while (reg & 0xff);
> +
> + /* ARM MCS value changed */
> + if (index == L4) {
> + reg = __raw_readl(S5P_ARM_MCS_CON);
> + reg &= ~0x3;
> + reg |= 0x3;
> + __raw_writel(reg, S5P_ARM_MCS_CON);
> + }
> +
> + if (pll_changing)
> + s5pv210_target_MPLL2APLL(index, bus_speed_changing);
> +
> + /* 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);
> + /* ONEDRAM Clock Divider Ratio: 7 for L4, 3 for Others */
> + __raw_writel(reg, S5P_CLK_DIV6);
> +
> + do {
> + reg = __raw_readl(S5P_CLK_DIV_STAT1);
> + } while (reg & (1 << 15));
> +
> + /* Reconfigure DRAM refresh counter value */
> + if (index != L4) {
> + /* DMC0: 166MHz
> + * DMC1: 200MHz
> + **/
> + __raw_writel(0x618, S5P_VA_DMC1 + 0x30);
> +#if !defined(CONFIG_S5PC110_H_TYPE)
> + __raw_writel(0x50e, S5P_VA_DMC0 + 0x30);
> +#else
> + __raw_writel(0x618, S5P_VA_DMC0 + 0x30);
> +#endif
> + } else {
> + /* DMC0: 83MHz
> + * DMC1: 100MHz
> + **/
> + __raw_writel(0x30c, S5P_VA_DMC1 + 0x30);
> + __raw_writel(0x287, S5P_VA_DMC0 + 0x30);
> + }
> + }
> +
> + if (s3c_freqs.freqs.new < s3c_freqs.freqs.old) {
> + /* Voltage down: decrease INT first.*/
> + if (!IS_ERR_OR_NULL(arm_regulator) &&
> + !IS_ERR_OR_NULL(internal_regulator)) {
> + regulator_set_voltage(internal_regulator,
> + int_volt, int_volt_max);
> + regulator_set_voltage(arm_regulator,
> + arm_volt, arm_volt_max);
> + }
> + }
> + cpufreq_notify_transition(&s3c_freqs.freqs, CPUFREQ_POSTCHANGE);
> +
> + memcpy(&s3c_freqs.old, &s3c_freqs.new, sizeof(struct s3c_freq));
> +#ifdef CONFIG_CPU_FREQ_DEBUG
> + printk(KERN_INFO "cpufreq: Performance changed[L%d]\n", index);
pr_info
> +#endif
> + previous_arm_volt = s5pv210_dvs_conf[index].arm_volt;
> +out:
> + return ret;
> +}
> +
> +
> +#ifdef CONFIG_PM
> +static int previous_frequency;
> +
> +static int s5pv210_cpufreq_suspend(struct cpufreq_policy *policy,
> + pm_message_t pmsg)
> +{
> + int ret = 0;
> + printk(KERN_INFO "cpufreq: Entering suspend.\n");
pr_info
> +
> + previous_frequency = cpufreq_get(0);
> + ret = __cpufreq_driver_target(cpufreq_cpu_get(0), SLEEP_FREQ,
> + DISABLE_FURTHER_CPUFREQ);
> + return ret;
> +}
> +
> +static int s5pv210_cpufreq_resume(struct cpufreq_policy *policy)
> +{
> + int ret = 0;
> + u32 rate;
> + int level = CPUFREQ_TABLE_END;
> + int i;
> +
> + printk(KERN_INFO "cpufreq: Waking up from a suspend.\n");
pr_info
> +
> + __cpufreq_driver_target(cpufreq_cpu_get(0), previous_frequency,
> + ENABLE_FURTHER_CPUFREQ);
> +
> + /* Clock information update with wakeup value */
> + rate = clk_get_rate(mpu_clk);
> +
> + i = 0;
> + while (s5pv210_freq_table[i].frequency != CPUFREQ_TABLE_END) {
> + if (s5pv210_freq_table[i].frequency * 1000 == rate) {
> + level = s5pv210_freq_table[i].index;
> + break;
> + }
> + i++;
> + }
> +
> + if (level == CPUFREQ_TABLE_END) { /* Not found */
> + printk(KERN_ERR "[%s:%d] clock speed does not match: "
> + "%d. Using L1 of 800MHz.\n",
> + __FILE__, __LINE__, rate);
pr_err
> + level = L1;
> + }
> +
> + memcpy(&s3c_freqs.old, &s5pv210_clk_info[level],
> + sizeof(struct s3c_freq));
> + previous_arm_volt = s5pv210_dvs_conf[level].arm_volt;
> + return ret;
> +}
> +#endif
> +
> +
> +static int __init s5pv210_cpu_init(struct cpufreq_policy *policy)
> +{
> + u32 rate ;
> + int i, level = CPUFREQ_TABLE_END;
> +
> + printk(KERN_INFO "S5PV210 CPUFREQ %s:%d\n", __FILE__, __LINE__);
pr_info
> +#ifdef CONFIG_PM
> + no_cpufreq_access = 0;
> +#endif
> +#ifdef CLK_OUT_PROBING
> + reg = __raw_readl(S5P_CLK_OUT);
> + reg &= ~(0x1f << 12 | 0xf << 20); /* CLKSEL and DIVVAL*/
> + reg |= (0xf << 12 | 0x1 << 20); /* CLKSEL = ARMCLK/4, DIVVAL = 1 */
> + /* Result = ARMCLK / 4 / ( 1 + 1 ) */
> + __raw_writel(reg, S5P_CLK_OUT);
> +#endif
> + mpu_clk = clk_get(NULL, MPU_CLK);
> + if (IS_ERR(mpu_clk)) {
> + printk(KERN_ERR "S5PV210 CPUFREQ cannot get MPU_CLK(%s)\n",
> + MPU_CLK);
pr_err
> + return PTR_ERR(mpu_clk);
> + }
> +
> + if (policy->cpu != 0) {
> + printk(KERN_ERR "S5PV210 CPUFREQ cannot get proper cpu(%d)\n",
> + policy->cpu);
pr_err
> + return -EINVAL;
> + }
> + policy->cur = policy->min = policy->max = s5pv210_getspeed(0);
> +
> + cpufreq_frequency_table_get_attr(s5pv210_freq_table, policy->cpu);
> +
> + policy->cpuinfo.transition_latency = 40000; /*1us*/
> +
> + rate = clk_get_rate(mpu_clk);
> + i = 0;
> +
> + while (s5pv210_freq_table[i].frequency != CPUFREQ_TABLE_END) {
> + if (s5pv210_freq_table[i].frequency * 1000 == rate) {
> + level = s5pv210_freq_table[i].index;
> + break;
> + }
> + i++;
> + }
> +
> + if (level == CPUFREQ_TABLE_END) { /* Not found */
> + printk(KERN_ERR "[%s:%d] clock speed does not match: "
> + "%d. Using L1 of 800MHz.\n",
> + __FILE__, __LINE__, rate);
> + level = L1;
> + }
> +
> + printk(KERN_INFO "S5PV210 CPUFREQ Initialized.\n");
pr_info
> +
> + memcpy(&s3c_freqs.old, &s5pv210_clk_info[level],
> + sizeof(struct s3c_freq));
> + previous_arm_volt = s5pv210_dvs_conf[level].arm_volt;
> +
> + 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)
> +{
> + printk(KERN_INFO "S5PV210 CPUFREQ Init.\n");
pr_info
Also, I guess one banner is enough?
> + arm_regulator = regulator_get_exclusive(NULL, "vddarm");
> + if (IS_ERR(arm_regulator)) {
> + printk(KERN_ERR "failed to get regulater resource vddarm\n");
> + goto error;
> + }
> + internal_regulator = regulator_get_exclusive(NULL, "vddint");
> + if (IS_ERR(internal_regulator)) {
> + printk(KERN_ERR "failed to get regulater resource vddint\n");
> + goto error;
> + }
> + goto finish;
> +error:
> + printk(KERN_WARNING "Cannot get vddarm or vddint. CPUFREQ Will not"
> + " change the voltage.\n");
pr_warn
> +finish:
> + return cpufreq_register_driver(&s5pv210_driver);
> +}
> +
> +late_initcall(s5pv210_cpufreq_init);
> diff --git a/arch/arm/mach-s5pv210/include/mach/cpu-freq.h b/arch/arm/mach-s5pv210/include/mach/cpu-freq.h
> new file mode 100644
> index 0000000..d3c31b2
> --- /dev/null
> +++ b/arch/arm/mach-s5pv210/include/mach/cpu-freq.h
> @@ -0,0 +1,50 @@
> +/* arch/arm/mach-s5pv210/include/mach/cpu-freq.h
> + *
> + * Copyright (c) 2010 Samsung Electronics
> + *
> + * MyungJoo Ham <myungjoo.ham at samsung.com>
> + *
> + * S5PV210/S5PC110 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.
> +*/
> +
> +#ifndef _ARCH_ARM_MACH_S5PV210_INCLUDE_MACH_CPU_FREQ_H_
> +#define _ARCH_ARM_MACH_S5PV210_INCLUDE_MACH_CPU_FREQ_H_
> +
> +#include <linux/cpufreq.h>
> +
> +#ifdef CONFIG_CPU_S5PV210
> +
> +#define USE_FREQ_TABLE
> +
> +#define KHZ_T 1000
> +
> +#define MPU_CLK "armclk"
> +
> +enum perf_level {
> + L0 = 0,
> + L1,
> + L2,
> + L3,
> + L4,
> +};
> +
> +#define CLK_DIV0_MASK ((0x7<<0)|(0x7<<8)|(0x7<<12)) /* APLL,HCLK_MSYS,PCLK_MSYS mask value */
> +
> +#ifdef CONFIG_PM
> +#define SLEEP_FREQ (800 * 1000) /* Use 800MHz when entering sleep */
> +
> +/* additional symantics for "relation" in cpufreq with pm */
> +#define DISABLE_FURTHER_CPUFREQ 0x10
> +#define ENABLE_FURTHER_CPUFREQ 0x20
> +#define MASK_FURTHER_CPUFREQ 0x30
> +/* With 0x00(NOCHANGE), it depends on the previous "further" status */
> +
> +#endif
> +
> +
> +#endif /* CONFIG_CPU_S5PV210 */
> +#endif /* _ARCH_ARM_MACH_S5PV210_INCLUDE_MACH_CPU_FREQ_H_ */
--
Maurus Cuelenaere
More information about the linux-arm-kernel
mailing list