Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f35f13ac authored by Saravana Kannan's avatar Saravana Kannan Committed by Rama Aparna Mallavarapu
Browse files

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: default avatarSaravana Kannan <skannan@codeaurora.org>
Signed-off-by: default avatarRama Aparna Mallavarapu <aparnam@codeaurora.org>
parent 768c41bd
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -127,6 +127,16 @@ config ARM_QCOM_DEVFREQ_FW
	  various governors could be used to scale the frequency of these
	  devices.

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
+1 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ 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_ARM_QCOM_DEVFREQ_FW)	+= devfreq_qcom_fw.o
obj-$(CONFIG_DEVFREQ_SIMPLE_DEV)	+= devfreq_simple_dev.o

# DEVFREQ Event Drivers
obj-$(CONFIG_PM_DEVFREQ_EVENT)		+= event/
+182 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2014, The Linux Foundation. All rights reserved.
 */

#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\n",
				 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_OR_ZERO(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");