linux/drivers/cpufreq/exynos4210-cpufreq.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
   3 *              http://www.samsung.com
   4 *
   5 * EXYNOS4210 - CPU frequency scaling support
   6 *
   7 * This program is free software; you can redistribute it and/or modify
   8 * it under the terms of the GNU General Public License version 2 as
   9 * published by the Free Software Foundation.
  10*/
  11
  12#include <linux/module.h>
  13#include <linux/kernel.h>
  14#include <linux/err.h>
  15#include <linux/clk.h>
  16#include <linux/io.h>
  17#include <linux/slab.h>
  18#include <linux/cpufreq.h>
  19
  20#include <mach/regs-clock.h>
  21
  22#include "exynos-cpufreq.h"
  23
  24static struct clk *cpu_clk;
  25static struct clk *moutcore;
  26static struct clk *mout_mpll;
  27static struct clk *mout_apll;
  28
  29static unsigned int exynos4210_volt_table[] = {
  30        1250000, 1150000, 1050000, 975000, 950000,
  31};
  32
  33static struct cpufreq_frequency_table exynos4210_freq_table[] = {
  34        {L0, 1200 * 1000},
  35        {L1, 1000 * 1000},
  36        {L2,  800 * 1000},
  37        {L3,  500 * 1000},
  38        {L4,  200 * 1000},
  39        {0, CPUFREQ_TABLE_END},
  40};
  41
  42static struct apll_freq apll_freq_4210[] = {
  43        /*
  44         * values:
  45         * freq
  46         * clock divider for CORE, COREM0, COREM1, PERIPH, ATB, PCLK_DBG, APLL, RESERVED
  47         * clock divider for COPY, HPM, RESERVED
  48         * PLL M, P, S
  49         */
  50        APLL_FREQ(1200, 0, 3, 7, 3, 4, 1, 7, 0, 5, 0, 0, 150, 3, 1),
  51        APLL_FREQ(1000, 0, 3, 7, 3, 4, 1, 7, 0, 4, 0, 0, 250, 6, 1),
  52        APLL_FREQ(800,  0, 3, 7, 3, 3, 1, 7, 0, 3, 0, 0, 200, 6, 1),
  53        APLL_FREQ(500,  0, 3, 7, 3, 3, 1, 7, 0, 3, 0, 0, 250, 6, 2),
  54        APLL_FREQ(200,  0, 1, 3, 1, 3, 1, 0, 0, 3, 0, 0, 200, 6, 3),
  55};
  56
  57static void exynos4210_set_clkdiv(unsigned int div_index)
  58{
  59        unsigned int tmp;
  60
  61        /* Change Divider - CPU0 */
  62
  63        tmp = apll_freq_4210[div_index].clk_div_cpu0;
  64
  65        __raw_writel(tmp, EXYNOS4_CLKDIV_CPU);
  66
  67        do {
  68                tmp = __raw_readl(EXYNOS4_CLKDIV_STATCPU);
  69        } while (tmp & 0x1111111);
  70
  71        /* Change Divider - CPU1 */
  72
  73        tmp = apll_freq_4210[div_index].clk_div_cpu1;
  74
  75        __raw_writel(tmp, EXYNOS4_CLKDIV_CPU1);
  76
  77        do {
  78                tmp = __raw_readl(EXYNOS4_CLKDIV_STATCPU1);
  79        } while (tmp & 0x11);
  80}
  81
  82static void exynos4210_set_apll(unsigned int index)
  83{
  84        unsigned int tmp;
  85
  86        /* 1. MUX_CORE_SEL = MPLL, ARMCLK uses MPLL for lock time */
  87        clk_set_parent(moutcore, mout_mpll);
  88
  89        do {
  90                tmp = (__raw_readl(EXYNOS4_CLKMUX_STATCPU)
  91                        >> EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT);
  92                tmp &= 0x7;
  93        } while (tmp != 0x2);
  94
  95        /* 2. Set APLL Lock time */
  96        __raw_writel(EXYNOS4_APLL_LOCKTIME, EXYNOS4_APLL_LOCK);
  97
  98        /* 3. Change PLL PMS values */
  99        tmp = __raw_readl(EXYNOS4_APLL_CON0);
 100        tmp &= ~((0x3ff << 16) | (0x3f << 8) | (0x7 << 0));
 101        tmp |= apll_freq_4210[index].mps;
 102        __raw_writel(tmp, EXYNOS4_APLL_CON0);
 103
 104        /* 4. wait_lock_time */
 105        do {
 106                tmp = __raw_readl(EXYNOS4_APLL_CON0);
 107        } while (!(tmp & (0x1 << EXYNOS4_APLLCON0_LOCKED_SHIFT)));
 108
 109        /* 5. MUX_CORE_SEL = APLL */
 110        clk_set_parent(moutcore, mout_apll);
 111
 112        do {
 113                tmp = __raw_readl(EXYNOS4_CLKMUX_STATCPU);
 114                tmp &= EXYNOS4_CLKMUX_STATCPU_MUXCORE_MASK;
 115        } while (tmp != (0x1 << EXYNOS4_CLKSRC_CPU_MUXCORE_SHIFT));
 116}
 117
 118static bool exynos4210_pms_change(unsigned int old_index, unsigned int new_index)
 119{
 120        unsigned int old_pm = apll_freq_4210[old_index].mps >> 8;
 121        unsigned int new_pm = apll_freq_4210[new_index].mps >> 8;
 122
 123        return (old_pm == new_pm) ? 0 : 1;
 124}
 125
 126static void exynos4210_set_frequency(unsigned int old_index,
 127                                     unsigned int new_index)
 128{
 129        unsigned int tmp;
 130
 131        if (old_index > new_index) {
 132                if (!exynos4210_pms_change(old_index, new_index)) {
 133                        /* 1. Change the system clock divider values */
 134                        exynos4210_set_clkdiv(new_index);
 135
 136                        /* 2. Change just s value in apll m,p,s value */
 137                        tmp = __raw_readl(EXYNOS4_APLL_CON0);
 138                        tmp &= ~(0x7 << 0);
 139                        tmp |= apll_freq_4210[new_index].mps & 0x7;
 140                        __raw_writel(tmp, EXYNOS4_APLL_CON0);
 141                } else {
 142                        /* Clock Configuration Procedure */
 143                        /* 1. Change the system clock divider values */
 144                        exynos4210_set_clkdiv(new_index);
 145                        /* 2. Change the apll m,p,s value */
 146                        exynos4210_set_apll(new_index);
 147                }
 148        } else if (old_index < new_index) {
 149                if (!exynos4210_pms_change(old_index, new_index)) {
 150                        /* 1. Change just s value in apll m,p,s value */
 151                        tmp = __raw_readl(EXYNOS4_APLL_CON0);
 152                        tmp &= ~(0x7 << 0);
 153                        tmp |= apll_freq_4210[new_index].mps & 0x7;
 154                        __raw_writel(tmp, EXYNOS4_APLL_CON0);
 155
 156                        /* 2. Change the system clock divider values */
 157                        exynos4210_set_clkdiv(new_index);
 158                } else {
 159                        /* Clock Configuration Procedure */
 160                        /* 1. Change the apll m,p,s value */
 161                        exynos4210_set_apll(new_index);
 162                        /* 2. Change the system clock divider values */
 163                        exynos4210_set_clkdiv(new_index);
 164                }
 165        }
 166}
 167
 168int exynos4210_cpufreq_init(struct exynos_dvfs_info *info)
 169{
 170        unsigned long rate;
 171
 172        cpu_clk = clk_get(NULL, "armclk");
 173        if (IS_ERR(cpu_clk))
 174                return PTR_ERR(cpu_clk);
 175
 176        moutcore = clk_get(NULL, "moutcore");
 177        if (IS_ERR(moutcore))
 178                goto err_moutcore;
 179
 180        mout_mpll = clk_get(NULL, "mout_mpll");
 181        if (IS_ERR(mout_mpll))
 182                goto err_mout_mpll;
 183
 184        rate = clk_get_rate(mout_mpll) / 1000;
 185
 186        mout_apll = clk_get(NULL, "mout_apll");
 187        if (IS_ERR(mout_apll))
 188                goto err_mout_apll;
 189
 190        info->mpll_freq_khz = rate;
 191        /* 800Mhz */
 192        info->pll_safe_idx = L2;
 193        info->cpu_clk = cpu_clk;
 194        info->volt_table = exynos4210_volt_table;
 195        info->freq_table = exynos4210_freq_table;
 196        info->set_freq = exynos4210_set_frequency;
 197        info->need_apll_change = exynos4210_pms_change;
 198
 199        return 0;
 200
 201err_mout_apll:
 202        clk_put(mout_mpll);
 203err_mout_mpll:
 204        clk_put(moutcore);
 205err_moutcore:
 206        clk_put(cpu_clk);
 207
 208        pr_debug("%s: failed initialization\n", __func__);
 209        return -EINVAL;
 210}
 211EXPORT_SYMBOL(exynos4210_cpufreq_init);
 212
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.