PM / devfreq: Add devfreq driver for simple device

This driver registers a devfreq device that allows devfreq governors to
scale the frequency of a device clock. This single driver can be used to
support multiple devices as long as those devices don't have a need or
mechanism to monitor their load and use clock APIs to control their
device/core clock.

If devices need to support device specific status monitoring, they could
extend this driver to allow registering device specific status monitoring
functions or write their own specific devfreq device driver.

Change-Id: Ie1797acf7b35cac6dc49428e270c23082634eb1e
Signed-off-by: Saravana Kannan <skannan@codeaurora.org>
This commit is contained in:
Saravana Kannan 2014-01-21 18:49:37 -08:00 committed by Jonathan Avila
parent d4dfec3ab0
commit 2161830f8b
4 changed files with 248 additions and 0 deletions

View File

@ -0,0 +1,47 @@
Devfreq simple device
devfreq-simple-dev is a device that represents a simple device that cannot do
any status reporting and uses a clock that can be scaled by one of more
devfreq governors. It provides a list of usable frequencies for the device
and some additional optional parameters.
Required properties:
- compatible: Must be "devfreq-simple-dev"
- clock-names: Must be "devfreq_clk"
- clocks: Must refer to the clock that's fed to the device.
- freq-tbl-khz: A list of usable frequencies (in KHz) for the device
clock.
Optional properties:
- polling-ms: Polling interval for the device in milliseconds. Default: 50
- governor: Initial governor to user for the device. Default: "performance"
Example:
qcom,cache {
compatible = "devfreq-simple-dev";
clock-names = "devfreq_clk";
clocks = <&clock_krait clk_l2_clk>;
polling-ms = 50;
governor = "cpufreq";
freq-tbl-khz =
< 300000 >,
< 345600 >,
< 422400 >,
< 499200 >,
< 576000 >,
< 652800 >,
< 729600 >,
< 806400 >,
< 883200 >,
< 960000 >,
< 1036800 >,
< 1113600 >,
< 1190400 >,
< 1267200 >,
< 1344000 >,
< 1420800 >,
< 1497600 >,
< 1574400 >,
< 1651200 >,
< 1728000 >;
};

View File

@ -113,6 +113,16 @@ config ARM_RK3399_DMC_DEVFREQ
It sets the frequency for the memory controller and reads the usage counts
from hardware.
config DEVFREQ_SIMPLE_DEV
tristate "Device driver for simple clock device with no status info"
select DEVFREQ_GOV_PERFORMANCE
select DEVFREQ_GOV_POWERSAVE
select DEVFREQ_GOV_USERSPACE
select DEVFREQ_GOV_CPUFREQ
help
Device driver for simple devices that control their frequency using
clock APIs and don't have any form of status reporting.
source "drivers/devfreq/event/Kconfig"
endif # PM_DEVFREQ

View File

@ -11,6 +11,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o
obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o
obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ) += rk3399_dmc.o
obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra-devfreq.o
obj-$(CONFIG_DEVFREQ_SIMPLE_DEV) += devfreq_simple_dev.o
# DEVFREQ Event Drivers
obj-$(CONFIG_PM_DEVFREQ_EVENT) += event/

View File

@ -0,0 +1,190 @@
/*
* Copyright (c) 2014, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "devfreq-simple-dev: " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/ktime.h>
#include <linux/time.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/devfreq.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <trace/events/power.h>
struct dev_data {
struct clk *clk;
struct devfreq *df;
struct devfreq_dev_profile profile;
};
static void find_freq(struct devfreq_dev_profile *p, unsigned long *freq,
u32 flags)
{
int i;
unsigned long atmost, atleast, f;
atmost = p->freq_table[0];
atleast = p->freq_table[p->max_state-1];
for (i = 0; i < p->max_state; i++) {
f = p->freq_table[i];
if (f <= *freq)
atmost = max(f, atmost);
if (f >= *freq)
atleast = min(f, atleast);
}
if (flags & DEVFREQ_FLAG_LEAST_UPPER_BOUND)
*freq = atmost;
else
*freq = atleast;
}
static int dev_target(struct device *dev, unsigned long *freq, u32 flags)
{
struct dev_data *d = dev_get_drvdata(dev);
unsigned long rfreq;
find_freq(&d->profile, freq, flags);
rfreq = clk_round_rate(d->clk, *freq * 1000);
if (IS_ERR_VALUE(rfreq)) {
dev_err(dev, "devfreq: Cannot find matching frequency for %lu\n",
*freq);
return rfreq;
}
return clk_set_rate(d->clk, rfreq);
}
static int dev_get_cur_freq(struct device *dev, unsigned long *freq)
{
struct dev_data *d = dev_get_drvdata(dev);
unsigned long f;
f = clk_get_rate(d->clk);
if (IS_ERR_VALUE(f))
return f;
*freq = f / 1000;
return 0;
}
#define PROP_TBL "freq-tbl-khz"
static int devfreq_clock_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct dev_data *d;
struct devfreq_dev_profile *p;
u32 *data, poll;
const char *gov_name;
int ret, len, i, j;
unsigned long f;
d = devm_kzalloc(dev, sizeof(*d), GFP_KERNEL);
if (!d)
return -ENOMEM;
platform_set_drvdata(pdev, d);
d->clk = devm_clk_get(dev, "devfreq_clk");
if (IS_ERR(d->clk))
return PTR_ERR(d->clk);
if (!of_find_property(dev->of_node, PROP_TBL, &len))
return -EINVAL;
len /= sizeof(*data);
data = devm_kzalloc(dev, len * sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
p = &d->profile;
p->freq_table = devm_kzalloc(dev, len * sizeof(*p->freq_table),
GFP_KERNEL);
if (!p->freq_table)
return -ENOMEM;
ret = of_property_read_u32_array(dev->of_node, PROP_TBL, data, len);
if (ret)
return ret;
j = 0;
for (i = 0; i < len; i++) {
f = clk_round_rate(d->clk, data[i] * 1000);
if (IS_ERR_VALUE(f))
dev_warn(dev, "Unable to find dev rate for %d KHz",
data[i]);
else
p->freq_table[j++] = f / 1000;
}
p->max_state = j;
devm_kfree(dev, data);
if (p->max_state == 0) {
dev_err(dev, "Error parsing property %s!\n", PROP_TBL);
return -EINVAL;
}
p->target = dev_target;
p->get_cur_freq = dev_get_cur_freq;
ret = dev_get_cur_freq(dev, &p->initial_freq);
if (ret)
return ret;
p->polling_ms = 50;
if (!of_property_read_u32(dev->of_node, "polling-ms", &poll))
p->polling_ms = poll;
if (of_property_read_string(dev->of_node, "governor", &gov_name))
gov_name = "performance";
d->df = devfreq_add_device(dev, p, gov_name, NULL);
if (IS_ERR(d->df))
return PTR_ERR(d->df);
return 0;
}
static int devfreq_clock_remove(struct platform_device *pdev)
{
struct dev_data *d = platform_get_drvdata(pdev);
devfreq_remove_device(d->df);
return 0;
}
static const struct of_device_id devfreq_simple_match_table[] = {
{ .compatible = "devfreq-simple-dev" },
{}
};
static struct platform_driver devfreq_clock_driver = {
.probe = devfreq_clock_probe,
.remove = devfreq_clock_remove,
.driver = {
.name = "devfreq-simple-dev",
.of_match_table = devfreq_simple_match_table,
},
};
module_platform_driver(devfreq_clock_driver);
MODULE_DESCRIPTION("Devfreq driver for setting generic device clock frequency");
MODULE_LICENSE("GPL v2");