[U-Boot] [PATCH 2/4 v2] Exynos5: cpufreq: Implement frequency scaling for exynos5

Akshay Saraswat akshay.s at samsung.com
Mon May 13 07:19:14 CEST 2013


Exynos5 currently runs at full speed i.e. 1.7 GHz everytime.
Scaling down the clock speed in certain situations, may help in
reducing the ARM temperature and power consumption.

Signed-off-by: Akshay Saraswat <akshay.s at samsung.com>
Acked-by: Simon Glass <sjg at chromium.org>
---
Changes since v1:
	- Added "Acked-by: Simon Glass".

 arch/arm/include/asm/arch-exynos/cpufreq.h |   54 ++++++
 drivers/power/Makefile                     |    1 +
 drivers/power/exynos-cpufreq.c             |  282 ++++++++++++++++++++++++++++
 3 files changed, 337 insertions(+)
 create mode 100644 arch/arm/include/asm/arch-exynos/cpufreq.h
 create mode 100644 drivers/power/exynos-cpufreq.c

diff --git a/arch/arm/include/asm/arch-exynos/cpufreq.h b/arch/arm/include/asm/arch-exynos/cpufreq.h
new file mode 100644
index 0000000..173e804
--- /dev/null
+++ b/arch/arm/include/asm/arch-exynos/cpufreq.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *      http://www.samsung.com
+ *
+ * EXYNOS - CPU frequency scaling support
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+/* Define various levels of ARM frequency */
+enum cpufreq_level {
+	CPU_FREQ_L200,		/* 200 MHz */
+	CPU_FREQ_L300,		/* 300 MHz */
+	CPU_FREQ_L400,		/* 400 MHz */
+	CPU_FREQ_L500,		/* 500 MHz */
+	CPU_FREQ_L600,		/* 600 MHz */
+	CPU_FREQ_L700,		/* 700 MHz */
+	CPU_FREQ_L800,		/* 800 MHz */
+	CPU_FREQ_L900,		/* 900 MHz */
+	CPU_FREQ_L1000,		/* 1000 MHz */
+	CPU_FREQ_L1100,		/* 1100 MHz */
+	CPU_FREQ_L1200,		/* 1200 MHz */
+	CPU_FREQ_L1300,		/* 1300 MHz */
+	CPU_FREQ_L1400,		/* 1400 MHz */
+	CPU_FREQ_L1500,		/* 1500 MHz */
+	CPU_FREQ_L1600,		/* 1600 MHz */
+	CPU_FREQ_L1700,		/* 1700 MHz */
+	CPU_FREQ_LCOUNT,
+};
+
+/*
+ * Initialize ARM frequency scaling
+ *
+ * @param blob  FDT blob
+ * @return	int value, 0 for success
+ */
+int exynos_cpufreq_init(void);
+
+/*
+ * Switch ARM frequency to new level
+ *
+ * @param new_freq_level	enum cpufreq_level, states new frequency
+ * @return			int value, 0 for success
+ */
+int exynos_set_frequency(enum cpufreq_level new_freq_level);
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 1dac16a..4589d9b 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -31,6 +31,7 @@ COBJS-$(CONFIG_TPS6586X_POWER)	+= tps6586x.o
 COBJS-$(CONFIG_TWL4030_POWER)	+= twl4030.o
 COBJS-$(CONFIG_TWL6030_POWER)	+= twl6030.o
 COBJS-$(CONFIG_TWL6035_POWER)	+= twl6035.o
+COBJS-$(CONFIG_EXYNOS_CPUFREQ)	+= exynos-cpufreq.o
 
 COBJS-$(CONFIG_POWER) += power_core.o
 COBJS-$(CONFIG_DIALOG_POWER) += power_dialog.o
diff --git a/drivers/power/exynos-cpufreq.c b/drivers/power/exynos-cpufreq.c
new file mode 100644
index 0000000..f473167
--- /dev/null
+++ b/drivers/power/exynos-cpufreq.c
@@ -0,0 +1,282 @@
+/*
+ * Copyright (c) 2012 Samsung Electronics Co., Ltd.
+ *      http://www.samsung.com
+ *
+ * EXYNOS - CPU frequency scaling support
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * 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.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include <common.h>
+#include <power/pmic.h>
+#include <asm/arch/clk.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/cpufreq.h>
+
+/* APLL CON0 */
+#define CON0_LOCK_BIT_MASK	(0x1 << 29)
+#define MDIV_MASK(x)		(x << 16)
+#define PDIV_MASK(x)		(x << 8)
+#define SDIV_MASK(x)		(x << 0)
+#define APLL_PMS_MASK		 ~(MDIV_MASK(0x3ff)	\
+				| PDIV_MASK(0x3f) | SDIV_MASK(0x7))
+
+/* MUX_STAT CPU select */
+#define MUX_CPU_NONE		(0x7 << 16)
+#define MUX_CPU_MOUT_APLL	(0x1 << 16)
+
+/* CLK_DIV_CPU0_VAL */
+#define DIV_CPU0_RSVD		~((0x7 << 28)	\
+				| (0x7 << 24)	\
+				| (0x7 << 20)	\
+				| (0x7 << 16)	\
+				| (0x7 << 12)	\
+				| (0x7 << 8)	\
+				| (0x7 << 4)	\
+				| (0x7))
+
+/* CLK_DIV_CPU1 */
+#define DIV_CPU1_RSVD		~((0x7 << 4) | (0x7))
+
+struct cpufreq_clkdiv {
+	uint8_t cpu0;
+	uint8_t cpu1;
+};
+
+struct cpufreq_data {
+	uint8_t arm;
+	uint8_t cpud;
+	uint8_t acp;
+	uint8_t periph;
+	uint8_t atb;
+	uint8_t pclk_dbg;
+	uint8_t apll;
+	uint8_t arm2;
+	uint8_t copy;
+	uint8_t hpm;
+	uint32_t apll_mdiv;
+	uint32_t apll_pdiv;
+	uint32_t apll_sdiv;
+	uint32_t volt;
+};
+
+static enum cpufreq_level old_freq_level;
+static struct cpufreq_clkdiv exynos5250_clkdiv;
+
+/*
+ * Clock divider, PMS and ASV group voltage values corresponding
+ * to frequencies.
+ */
+static struct cpufreq_data exynos5250_data_table[CPU_FREQ_LCOUNT] = {
+	/*
+	 * { ARM, CPUD, ACP, PERIPH, ATB, PCLK_DBG, APLL, ARM2, COPY, HPM,
+	 * APLL_MDIV, APLL_PDIV, APLL_SDIV, VOLTAGE }
+	 */
+	{ 0, 1, 7, 7, 1, 1, 1, 0, 0, 2, 100, 3, 2, 925000 },	/* 200 MHz */
+	{ 0, 1, 7, 7, 1, 1, 1, 0, 0, 2, 200, 4, 2, 937500 },	/* 300 MHz */
+	{ 0, 1, 7, 7, 2, 1, 1, 0, 0, 2, 100, 3, 1, 950000 },	/* 400 MHz */
+	{ 0, 1, 7, 7, 2, 1, 1, 0, 0, 2, 125, 3, 1, 975000 },	/* 500 MHz */
+	{ 0, 1, 7, 7, 3, 1, 1, 0, 0, 2, 200, 4, 1, 1000000 },	/* 600 MHz */
+	{ 0, 1, 7, 7, 3, 1, 1, 0, 0, 2, 175, 3, 1, 1012500 },	/* 700 MHz */
+	{ 0, 1, 7, 7, 4, 1, 2, 0, 0, 2, 100, 3, 0, 1025000 },	/* 800 MHz */
+	{ 0, 1, 7, 7, 4, 1, 2, 0, 0, 2, 150, 4, 0, 1050000 },	/* 900 MHz */
+	{ 0, 1, 7, 7, 4, 1, 2, 0, 0, 2, 125, 3, 0, 1075000 },	/* 1000 MHz */
+	{ 0, 3, 7, 7, 5, 1, 3, 0, 0, 2, 275, 6, 0, 1100000 },	/* 1100 MHz */
+	{ 0, 2, 7, 7, 5, 1, 3, 0, 0, 2, 200, 4, 0, 1125000 },	/* 1200 MHz */
+	{ 0, 2, 7, 7, 6, 1, 3, 0, 0, 2, 325, 6, 0, 1150000 },	/* 1300 MHz */
+	{ 0, 2, 7, 7, 6, 1, 4, 0, 0, 2, 175, 3, 0, 1200000 },	/* 1400 MHz */
+	{ 0, 2, 7, 7, 7, 1, 4, 0, 0, 2, 250, 4, 0, 1225000 },	/* 1500 MHz */
+	{ 0, 3, 7, 7, 7, 1, 4, 0, 0, 2, 200, 3, 0, 1250000 },	/* 1600 MHz */
+	{ 0, 3, 7, 7, 7, 2, 5, 0, 0, 2, 425, 6, 0, 1300000 },	/* 1700 MHz */
+};
+
+/*
+ * Set clock divider values to alter exynos5 frequency
+ *
+ * @param new_freq_level	enum cpufreq_level, states new frequency
+ * @param clk			struct exynos5_clock *,
+ *				provides clock reg addresses
+ */
+static void exynos5_set_clkdiv(enum cpufreq_level new_freq_level,
+					struct exynos5_clock *clk)
+{
+	unsigned int val;
+
+	/* Change Divider - CPU0 */
+	val = exynos5250_clkdiv.cpu0;
+
+	val |= (exynos5250_data_table[new_freq_level].arm << 0) |
+		(exynos5250_data_table[new_freq_level].cpud << 4) |
+		(exynos5250_data_table[new_freq_level].acp << 8) |
+		(exynos5250_data_table[new_freq_level].periph << 12) |
+		(exynos5250_data_table[new_freq_level].atb << 16) |
+		(exynos5250_data_table[new_freq_level].pclk_dbg << 20) |
+		(exynos5250_data_table[new_freq_level].apll << 24) |
+		(exynos5250_data_table[new_freq_level].arm2 << 28);
+
+	writel(val, &clk->div_cpu0);
+
+	/* Wait for CPU0 divider to be stable */
+	while (readl(&clk->div_stat_cpu0) & 0x11111111)
+		;
+
+	/* Change Divider - CPU1 */
+	val = exynos5250_clkdiv.cpu1;
+
+	val |= (exynos5250_data_table[new_freq_level].copy << 0) |
+		(exynos5250_data_table[new_freq_level].hpm << 4);
+
+	writel(val, &clk->div_cpu1);
+
+	/* Wait for CPU1 divider to be stable */
+	while (readl(&clk->div_stat_cpu1) & 0x11)
+		;
+}
+
+/*
+ * Set APLL values to alter exynos5 frequency
+ *
+ * @param new_freq_level	enum cpufreq_level, states new frequency
+ * @param clk			struct exynos5_clock *,
+ *				provides clock reg addresses
+ */
+static void exynos5_set_apll(enum cpufreq_level new_freq_level,
+					struct exynos5_clock *clk)
+{
+	unsigned int val, pdiv;
+
+	/* Set APLL Lock time */
+	pdiv = exynos5250_data_table[new_freq_level].apll_pdiv;
+	writel((pdiv * 250), &clk->apll_lock);
+
+	/* Change PLL PMS values */
+	val = readl(&clk->apll_con0);
+	val &= APLL_PMS_MASK;
+	val |= MDIV_MASK(exynos5250_data_table[new_freq_level].apll_mdiv) |
+		PDIV_MASK(exynos5250_data_table[new_freq_level].apll_pdiv) |
+		SDIV_MASK(exynos5250_data_table[new_freq_level].apll_sdiv);
+	writel(val, &clk->apll_con0);
+
+	/* Wait for APLL lock time to complete */
+	while (!(readl(&clk->apll_con0) & CON0_LOCK_BIT_MASK))
+		;
+}
+
+/*
+ * Switch ARM power corresponding to new frequency level
+ *
+ * @param new_volt_index	enum cpufreq_level, provides new voltage
+ *				corresponing to new frequency level
+ * @return			int value, 0 for success
+ */
+static int exynos5_set_voltage(enum cpufreq_level new_volt_index)
+{
+	u32 new_volt;
+
+	new_volt =  exynos5250_data_table[new_volt_index].volt;
+
+	return pmic_set_voltage(new_volt);
+}
+
+/*
+ * Switch exybos5 frequency to new level
+ *
+ * @param new_freq_level	enum cpufreq_level, provides new frequency
+ * @return			int value, 0 for success
+ */
+static int exynos5_set_frequency(enum cpufreq_level new_freq_level)
+{
+	int error = 0;
+	struct exynos5_clock *clk =
+		(struct exynos5_clock *)samsung_get_base_clock();
+
+	if (old_freq_level < new_freq_level) {
+		/* Alter voltage corresponding to new frequency */
+		error = exynos5_set_voltage(new_freq_level);
+
+		/* Change the system clock divider values */
+		exynos5_set_clkdiv(new_freq_level, clk);
+
+		/* Change the apll m,p,s value */
+		exynos5_set_apll(new_freq_level, clk);
+	} else if (old_freq_level > new_freq_level) {
+		/* Change the apll m,p,s value */
+		exynos5_set_apll(new_freq_level, clk);
+
+		/* Change the system clock divider values */
+		exynos5_set_clkdiv(new_freq_level, clk);
+
+		/* Alter voltage corresponding to new frequency */
+		error = exynos5_set_voltage(new_freq_level);
+	}
+
+	old_freq_level = new_freq_level;
+	debug("ARM Frequency changed\n");
+
+	return error;
+}
+
+/*
+ * Switch ARM frequency to new level
+ *
+ * @param new_freq_level	enum cpufreq_level, states new frequency
+ * @return			int value, 0 for success
+ */
+int exynos_set_frequency(enum cpufreq_level new_freq_level)
+{
+	if (cpu_is_exynos5()) {
+		return exynos5_set_frequency(new_freq_level);
+	} else {
+		debug("CPUFREQ: Frequency scaling not allowed for this CPU\n");
+		return -1;
+	}
+}
+
+/*
+ * Initialize frequency scaling for exynos5
+ */
+static void exynos5_cpufreq_init(void)
+{
+	unsigned int val;
+	struct exynos5_clock *clk =
+		(struct exynos5_clock *)samsung_get_base_clock();
+
+	/* Save default divider ratios for CPU0 */
+	val = readl(&clk->div_cpu0);
+	val &= DIV_CPU0_RSVD;
+	exynos5250_clkdiv.cpu0 = val;
+
+	/* Save default divider ratios for CPU1 */
+	val = readl(&clk->div_cpu1);
+	val &= DIV_CPU1_RSVD;
+	exynos5250_clkdiv.cpu1 = val;
+
+	/* Calculate default ARM frequence level */
+	old_freq_level = (get_pll_clk(APLL) / 100000000) - 2;
+	debug("Current ARM frequency is %u\n", val);
+}
+
+/*
+ * Initialize ARM frequency scaling
+ *
+ * @return	int value, 0 for success
+ */
+int exynos_cpufreq_init(void)
+{
+	if (cpu_is_exynos5()) {
+		exynos5_cpufreq_init();
+		return 0;
+	} else {
+		debug("CPUFREQ: Could not init for this CPU\n");
+		return -1;
+	}
+}
-- 
1.7.10.4



More information about the U-Boot mailing list