[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