linux/drivers/cpufreq/scpi-cpufreq.c
<<
>>
Prefs
   1/*
   2 * System Control and Power Interface (SCPI) based CPUFreq Interface driver
   3 *
   4 * It provides necessary ops to arm_big_little cpufreq driver.
   5 *
   6 * Copyright (C) 2015 ARM Ltd.
   7 * Sudeep Holla <sudeep.holla@arm.com>
   8 *
   9 * This program is free software; you can redistribute it and/or modify
  10 * it under the terms of the GNU General Public License version 2 as
  11 * published by the Free Software Foundation.
  12 *
  13 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
  14 * kind, whether express or implied; without even the implied warranty
  15 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16 * GNU General Public License for more details.
  17 */
  18
  19#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  20
  21#include <linux/cpu.h>
  22#include <linux/cpufreq.h>
  23#include <linux/module.h>
  24#include <linux/platform_device.h>
  25#include <linux/pm_opp.h>
  26#include <linux/scpi_protocol.h>
  27#include <linux/types.h>
  28
  29#include "arm_big_little.h"
  30
  31static struct scpi_ops *scpi_ops;
  32
  33static struct scpi_dvfs_info *scpi_get_dvfs_info(struct device *cpu_dev)
  34{
  35        int domain = topology_physical_package_id(cpu_dev->id);
  36
  37        if (domain < 0)
  38                return ERR_PTR(-EINVAL);
  39        return scpi_ops->dvfs_get_info(domain);
  40}
  41
  42static int scpi_get_transition_latency(struct device *cpu_dev)
  43{
  44        struct scpi_dvfs_info *info = scpi_get_dvfs_info(cpu_dev);
  45
  46        if (IS_ERR(info))
  47                return PTR_ERR(info);
  48        return info->latency;
  49}
  50
  51static int scpi_init_opp_table(const struct cpumask *cpumask)
  52{
  53        int idx, ret;
  54        struct scpi_opp *opp;
  55        struct device *cpu_dev = get_cpu_device(cpumask_first(cpumask));
  56        struct scpi_dvfs_info *info = scpi_get_dvfs_info(cpu_dev);
  57
  58        if (IS_ERR(info))
  59                return PTR_ERR(info);
  60
  61        if (!info->opps)
  62                return -EIO;
  63
  64        for (opp = info->opps, idx = 0; idx < info->count; idx++, opp++) {
  65                ret = dev_pm_opp_add(cpu_dev, opp->freq, opp->m_volt * 1000);
  66                if (ret) {
  67                        dev_warn(cpu_dev, "failed to add opp %uHz %umV\n",
  68                                 opp->freq, opp->m_volt);
  69                        while (idx-- > 0)
  70                                dev_pm_opp_remove(cpu_dev, (--opp)->freq);
  71                        return ret;
  72                }
  73        }
  74
  75        ret = dev_pm_opp_set_sharing_cpus(cpu_dev, cpumask);
  76        if (ret)
  77                dev_err(cpu_dev, "%s: failed to mark OPPs as shared: %d\n",
  78                        __func__, ret);
  79        return ret;
  80}
  81
  82static struct cpufreq_arm_bL_ops scpi_cpufreq_ops = {
  83        .name   = "scpi",
  84        .get_transition_latency = scpi_get_transition_latency,
  85        .init_opp_table = scpi_init_opp_table,
  86        .free_opp_table = dev_pm_opp_cpumask_remove_table,
  87};
  88
  89static int scpi_cpufreq_probe(struct platform_device *pdev)
  90{
  91        scpi_ops = get_scpi_ops();
  92        if (!scpi_ops)
  93                return -EIO;
  94
  95        return bL_cpufreq_register(&scpi_cpufreq_ops);
  96}
  97
  98static int scpi_cpufreq_remove(struct platform_device *pdev)
  99{
 100        bL_cpufreq_unregister(&scpi_cpufreq_ops);
 101        scpi_ops = NULL;
 102        return 0;
 103}
 104
 105static struct platform_driver scpi_cpufreq_platdrv = {
 106        .driver = {
 107                .name   = "scpi-cpufreq",
 108        },
 109        .probe          = scpi_cpufreq_probe,
 110        .remove         = scpi_cpufreq_remove,
 111};
 112module_platform_driver(scpi_cpufreq_platdrv);
 113
 114MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");
 115MODULE_DESCRIPTION("ARM SCPI CPUFreq interface driver");
 116MODULE_LICENSE("GPL v2");
 117