linux/drivers/cpufreq/exynos-cpufreq.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
   3 *              http://www.samsung.com
   4 *
   5 * EXYNOS - CPU frequency scaling support for EXYNOS series
   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/kernel.h>
  13#include <linux/err.h>
  14#include <linux/clk.h>
  15#include <linux/io.h>
  16#include <linux/slab.h>
  17#include <linux/regulator/consumer.h>
  18#include <linux/cpufreq.h>
  19#include <linux/platform_device.h>
  20#include <linux/of.h>
  21
  22#include "exynos-cpufreq.h"
  23
  24static struct exynos_dvfs_info *exynos_info;
  25static struct regulator *arm_regulator;
  26static unsigned int locking_frequency;
  27
  28static int exynos_cpufreq_get_index(unsigned int freq)
  29{
  30        struct cpufreq_frequency_table *freq_table = exynos_info->freq_table;
  31        struct cpufreq_frequency_table *pos;
  32
  33        cpufreq_for_each_entry(pos, freq_table)
  34                if (pos->frequency == freq)
  35                        break;
  36
  37        if (pos->frequency == CPUFREQ_TABLE_END)
  38                return -EINVAL;
  39
  40        return pos - freq_table;
  41}
  42
  43static int exynos_cpufreq_scale(unsigned int target_freq)
  44{
  45        struct cpufreq_frequency_table *freq_table = exynos_info->freq_table;
  46        unsigned int *volt_table = exynos_info->volt_table;
  47        struct cpufreq_policy *policy = cpufreq_cpu_get(0);
  48        unsigned int arm_volt, safe_arm_volt = 0;
  49        unsigned int mpll_freq_khz = exynos_info->mpll_freq_khz;
  50        struct device *dev = exynos_info->dev;
  51        unsigned int old_freq;
  52        int index, old_index;
  53        int ret = 0;
  54
  55        old_freq = policy->cur;
  56
  57        /*
  58         * The policy max have been changed so that we cannot get proper
  59         * old_index with cpufreq_frequency_table_target(). Thus, ignore
  60         * policy and get the index from the raw frequency table.
  61         */
  62        old_index = exynos_cpufreq_get_index(old_freq);
  63        if (old_index < 0) {
  64                ret = old_index;
  65                goto out;
  66        }
  67
  68        index = exynos_cpufreq_get_index(target_freq);
  69        if (index < 0) {
  70                ret = index;
  71                goto out;
  72        }
  73
  74        /*
  75         * ARM clock source will be changed APLL to MPLL temporary
  76         * To support this level, need to control regulator for
  77         * required voltage level
  78         */
  79        if (exynos_info->need_apll_change != NULL) {
  80                if (exynos_info->need_apll_change(old_index, index) &&
  81                   (freq_table[index].frequency < mpll_freq_khz) &&
  82                   (freq_table[old_index].frequency < mpll_freq_khz))
  83                        safe_arm_volt = volt_table[exynos_info->pll_safe_idx];
  84        }
  85        arm_volt = volt_table[index];
  86
  87        /* When the new frequency is higher than current frequency */
  88        if ((target_freq > old_freq) && !safe_arm_volt) {
  89                /* Firstly, voltage up to increase frequency */
  90                ret = regulator_set_voltage(arm_regulator, arm_volt, arm_volt);
  91                if (ret) {
  92                        dev_err(dev, "failed to set cpu voltage to %d\n",
  93                                arm_volt);
  94                        return ret;
  95                }
  96        }
  97
  98        if (safe_arm_volt) {
  99                ret = regulator_set_voltage(arm_regulator, safe_arm_volt,
 100                                      safe_arm_volt);
 101                if (ret) {
 102                        dev_err(dev, "failed to set cpu voltage to %d\n",
 103                                safe_arm_volt);
 104                        return ret;
 105                }
 106        }
 107
 108        exynos_info->set_freq(old_index, index);
 109
 110        /* When the new frequency is lower than current frequency */
 111        if ((target_freq < old_freq) ||
 112           ((target_freq > old_freq) && safe_arm_volt)) {
 113                /* down the voltage after frequency change */
 114                ret = regulator_set_voltage(arm_regulator, arm_volt,
 115                                arm_volt);
 116                if (ret) {
 117                        dev_err(dev, "failed to set cpu voltage to %d\n",
 118                                arm_volt);
 119                        goto out;
 120                }
 121        }
 122
 123out:
 124        cpufreq_cpu_put(policy);
 125
 126        return ret;
 127}
 128
 129static int exynos_target(struct cpufreq_policy *policy, unsigned int index)
 130{
 131        return exynos_cpufreq_scale(exynos_info->freq_table[index].frequency);
 132}
 133
 134static int exynos_cpufreq_cpu_init(struct cpufreq_policy *policy)
 135{
 136        policy->clk = exynos_info->cpu_clk;
 137        policy->suspend_freq = locking_frequency;
 138        return cpufreq_generic_init(policy, exynos_info->freq_table, 100000);
 139}
 140
 141static struct cpufreq_driver exynos_driver = {
 142        .flags          = CPUFREQ_STICKY | CPUFREQ_NEED_INITIAL_FREQ_CHECK,
 143        .verify         = cpufreq_generic_frequency_table_verify,
 144        .target_index   = exynos_target,
 145        .get            = cpufreq_generic_get,
 146        .init           = exynos_cpufreq_cpu_init,
 147        .name           = "exynos_cpufreq",
 148        .attr           = cpufreq_generic_attr,
 149#ifdef CONFIG_ARM_EXYNOS_CPU_FREQ_BOOST_SW
 150        .boost_supported = true,
 151#endif
 152#ifdef CONFIG_PM
 153        .suspend        = cpufreq_generic_suspend,
 154#endif
 155};
 156
 157static int exynos_cpufreq_probe(struct platform_device *pdev)
 158{
 159        int ret = -EINVAL;
 160
 161        exynos_info = kzalloc(sizeof(*exynos_info), GFP_KERNEL);
 162        if (!exynos_info)
 163                return -ENOMEM;
 164
 165        exynos_info->dev = &pdev->dev;
 166
 167        if (of_machine_is_compatible("samsung,exynos4210")) {
 168                exynos_info->type = EXYNOS_SOC_4210;
 169                ret = exynos4210_cpufreq_init(exynos_info);
 170        } else if (of_machine_is_compatible("samsung,exynos4212")) {
 171                exynos_info->type = EXYNOS_SOC_4212;
 172                ret = exynos4x12_cpufreq_init(exynos_info);
 173        } else if (of_machine_is_compatible("samsung,exynos4412")) {
 174                exynos_info->type = EXYNOS_SOC_4412;
 175                ret = exynos4x12_cpufreq_init(exynos_info);
 176        } else if (of_machine_is_compatible("samsung,exynos5250")) {
 177                exynos_info->type = EXYNOS_SOC_5250;
 178                ret = exynos5250_cpufreq_init(exynos_info);
 179        } else {
 180                pr_err("%s: Unknown SoC type\n", __func__);
 181                return -ENODEV;
 182        }
 183
 184        if (ret)
 185                goto err_vdd_arm;
 186
 187        if (exynos_info->set_freq == NULL) {
 188                dev_err(&pdev->dev, "No set_freq function (ERR)\n");
 189                goto err_vdd_arm;
 190        }
 191
 192        arm_regulator = regulator_get(NULL, "vdd_arm");
 193        if (IS_ERR(arm_regulator)) {
 194                dev_err(&pdev->dev, "failed to get resource vdd_arm\n");
 195                goto err_vdd_arm;
 196        }
 197
 198        /* Done here as we want to capture boot frequency */
 199        locking_frequency = clk_get_rate(exynos_info->cpu_clk) / 1000;
 200
 201        if (!cpufreq_register_driver(&exynos_driver))
 202                return 0;
 203
 204        dev_err(&pdev->dev, "failed to register cpufreq driver\n");
 205        regulator_put(arm_regulator);
 206err_vdd_arm:
 207        kfree(exynos_info);
 208        return -EINVAL;
 209}
 210
 211static struct platform_driver exynos_cpufreq_platdrv = {
 212        .driver = {
 213                .name   = "exynos-cpufreq",
 214                .owner  = THIS_MODULE,
 215        },
 216        .probe = exynos_cpufreq_probe,
 217};
 218module_platform_driver(exynos_cpufreq_platdrv);
 219
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.