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 <aksha...@samsung.com>
Acked-by: Simon Glass <s...@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

_______________________________________________
U-Boot mailing list
U-Boot@lists.denx.de
http://lists.denx.de/mailman/listinfo/u-boot

Reply via email to