Commit ac9f3109 authored by Rafael J. Wysocki's avatar Rafael J. Wysocki
Browse files

Merge branch 'powercap'

Merge Dynamic Thermal Power Management (DTPM) changes for 5.18-rc1:

 - Add DTPM hierarchy description (Daniel Lezcano).

 - Change the locking scheme in DTPM (Daniel Lezcano).

 - Fix dtpm_cpu cleanup at exit time and missing virtual DTPM pointer
   release (Daniel Lezcano).

 - Make dtpm_node_callback[] static (kernel test robot).

 - Fix spelling mistake "initialze" -> "initialize" in
   dtpm_create_hierarchy() (Colin Ian King).

* powercap:
  powercap: DTPM: Fix spelling mistake "initialze" -> "initialize"
  powercap: DTPM: dtpm_node_callback[] can be static
  dtpm/soc/rk3399: Add the ability to unload the module
  powercap/dtpm_cpu: Add exit function
  powercap/dtpm: Move the 'root' reset place
  powercap/dtpm: Destroy hierarchy function
  powercap/dtpm: Fixup kfree for virtual node
  powercap/dtpm_cpu: Reset per_cpu variable in the release function
  powercap/dtpm: Change locking scheme
  rockchip/soc/drivers: Add DTPM description for rk3399
  powercap/drivers/dtpm: Add dtpm devfreq with energy model support
  powercap/drivers/dtpm: Add CPU DT initialization support
  powercap/drivers/dtpm: Add hierarchy creation
  powercap/drivers/dtpm: Convert the init table section to a simple array
parents dfad78e0 55ddcd9f
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ config IDLE_INJECT

config DTPM
	bool "Power capping for Dynamic Thermal Power Management (EXPERIMENTAL)"
	depends on OF
	help
	  This enables support for the power capping for the dynamic
	  thermal power management userspace engine.
@@ -56,4 +57,11 @@ config DTPM_CPU
	help
	  This enables support for CPU power limitation based on
	  energy model.

config DTPM_DEVFREQ
	bool "Add device power capping based on the energy model"
	depends on DTPM && ENERGY_MODEL
	help
	  This enables support for device power limitation based on
	  energy model.
endif
+1 −0
Original line number Diff line number Diff line
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_DTPM) += dtpm.o
obj-$(CONFIG_DTPM_CPU) += dtpm_cpu.o
obj-$(CONFIG_DTPM_DEVFREQ) += dtpm_devfreq.o
obj-$(CONFIG_POWERCAP)	+= powercap_sys.o
obj-$(CONFIG_INTEL_RAPL_CORE) += intel_rapl_common.o
obj-$(CONFIG_INTEL_RAPL) += intel_rapl_msr.o
+260 −73
Original line number Diff line number Diff line
@@ -23,6 +23,9 @@
#include <linux/powercap.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/of.h>

#include "dtpm_subsys.h"

#define DTPM_POWER_LIMIT_FLAG 0

@@ -48,9 +51,7 @@ static int get_max_power_range_uw(struct powercap_zone *pcz, u64 *max_power_uw)
{
	struct dtpm *dtpm = to_dtpm(pcz);

	mutex_lock(&dtpm_lock);
	*max_power_uw = dtpm->power_max - dtpm->power_min;
	mutex_unlock(&dtpm_lock);

	return 0;
}
@@ -80,14 +81,7 @@ static int __get_power_uw(struct dtpm *dtpm, u64 *power_uw)

static int get_power_uw(struct powercap_zone *pcz, u64 *power_uw)
{
	struct dtpm *dtpm = to_dtpm(pcz);
	int ret;

	mutex_lock(&dtpm_lock);
	ret = __get_power_uw(dtpm, power_uw);
	mutex_unlock(&dtpm_lock);

	return ret;
	return __get_power_uw(to_dtpm(pcz), power_uw);
}

static void __dtpm_rebalance_weight(struct dtpm *dtpm)
@@ -130,7 +124,16 @@ static void __dtpm_add_power(struct dtpm *dtpm)
	}
}

static int __dtpm_update_power(struct dtpm *dtpm)
/**
 * dtpm_update_power - Update the power on the dtpm
 * @dtpm: a pointer to a dtpm structure to update
 *
 * Function to update the power values of the dtpm node specified in
 * parameter. These new values will be propagated to the tree.
 *
 * Return: zero on success, -EINVAL if the values are inconsistent
 */
int dtpm_update_power(struct dtpm *dtpm)
{
	int ret;

@@ -152,26 +155,6 @@ static int __dtpm_update_power(struct dtpm *dtpm)
	return ret;
}

/**
 * dtpm_update_power - Update the power on the dtpm
 * @dtpm: a pointer to a dtpm structure to update
 *
 * Function to update the power values of the dtpm node specified in
 * parameter. These new values will be propagated to the tree.
 *
 * Return: zero on success, -EINVAL if the values are inconsistent
 */
int dtpm_update_power(struct dtpm *dtpm)
{
	int ret;

	mutex_lock(&dtpm_lock);
	ret = __dtpm_update_power(dtpm);
	mutex_unlock(&dtpm_lock);

	return ret;
}

/**
 * dtpm_release_zone - Cleanup when the node is released
 * @pcz: a pointer to a powercap_zone structure
@@ -188,48 +171,28 @@ int dtpm_release_zone(struct powercap_zone *pcz)
	struct dtpm *dtpm = to_dtpm(pcz);
	struct dtpm *parent = dtpm->parent;

	mutex_lock(&dtpm_lock);

	if (!list_empty(&dtpm->children)) {
		mutex_unlock(&dtpm_lock);
	if (!list_empty(&dtpm->children))
		return -EBUSY;
	}

	if (parent)
		list_del(&dtpm->sibling);

	__dtpm_sub_power(dtpm);

	mutex_unlock(&dtpm_lock);

	if (dtpm->ops)
		dtpm->ops->release(dtpm);

	if (root == dtpm)
		root = NULL;

	else
		kfree(dtpm);

	return 0;
}

static int __get_power_limit_uw(struct dtpm *dtpm, int cid, u64 *power_limit)
{
	*power_limit = dtpm->power_limit;
	return 0;
}

static int get_power_limit_uw(struct powercap_zone *pcz,
			      int cid, u64 *power_limit)
{
	struct dtpm *dtpm = to_dtpm(pcz);
	int ret;

	mutex_lock(&dtpm_lock);
	ret = __get_power_limit_uw(dtpm, cid, power_limit);
	mutex_unlock(&dtpm_lock);
	*power_limit = to_dtpm(pcz)->power_limit;
	
	return ret;
	return 0;
}

/*
@@ -289,7 +252,7 @@ static int __set_power_limit_uw(struct dtpm *dtpm, int cid, u64 power_limit)

			ret = __set_power_limit_uw(child, cid, power);
			if (!ret)
				ret = __get_power_limit_uw(child, cid, &power);
				ret = get_power_limit_uw(&child->zone, cid, &power);

			if (ret)
				break;
@@ -307,8 +270,6 @@ static int set_power_limit_uw(struct powercap_zone *pcz,
	struct dtpm *dtpm = to_dtpm(pcz);
	int ret;

	mutex_lock(&dtpm_lock);

	/*
	 * Don't allow values outside of the power range previously
	 * set when initializing the power numbers.
@@ -320,8 +281,6 @@ static int set_power_limit_uw(struct powercap_zone *pcz,
	pr_debug("%s: power limit: %llu uW, power max: %llu uW\n",
		 dtpm->zone.name, dtpm->power_limit, dtpm->power_max);

	mutex_unlock(&dtpm_lock);

	return ret;
}

@@ -332,11 +291,7 @@ static const char *get_constraint_name(struct powercap_zone *pcz, int cid)

static int get_max_power_uw(struct powercap_zone *pcz, int id, u64 *max_power)
{
	struct dtpm *dtpm = to_dtpm(pcz);

	mutex_lock(&dtpm_lock);
	*max_power = dtpm->power_max;
	mutex_unlock(&dtpm_lock);
	*max_power = to_dtpm(pcz)->power_max;

	return 0;
}
@@ -439,8 +394,6 @@ int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent)
	if (IS_ERR(pcz))
		return PTR_ERR(pcz);

	mutex_lock(&dtpm_lock);

	if (parent) {
		list_add_tail(&dtpm->sibling, &parent->children);
		dtpm->parent = parent;
@@ -456,19 +409,253 @@ int dtpm_register(const char *name, struct dtpm *dtpm, struct dtpm *parent)
	pr_debug("Registered dtpm node '%s' / %llu-%llu uW, \n",
		 dtpm->zone.name, dtpm->power_min, dtpm->power_max);

	mutex_unlock(&dtpm_lock);
	return 0;
}

static struct dtpm *dtpm_setup_virtual(const struct dtpm_node *hierarchy,
				       struct dtpm *parent)
{
	struct dtpm *dtpm;
	int ret;

	dtpm = kzalloc(sizeof(*dtpm), GFP_KERNEL);
	if (!dtpm)
		return ERR_PTR(-ENOMEM);
	dtpm_init(dtpm, NULL);

	ret = dtpm_register(hierarchy->name, dtpm, parent);
	if (ret) {
		pr_err("Failed to register dtpm node '%s': %d\n",
		       hierarchy->name, ret);
		kfree(dtpm);
		return ERR_PTR(ret);
	}

	return dtpm;
}

static struct dtpm *dtpm_setup_dt(const struct dtpm_node *hierarchy,
				  struct dtpm *parent)
{
	struct device_node *np;
	int i, ret;

	np = of_find_node_by_path(hierarchy->name);
	if (!np) {
		pr_err("Failed to find '%s'\n", hierarchy->name);
		return ERR_PTR(-ENXIO);
	}

	for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {

		if (!dtpm_subsys[i]->setup)
			continue;

		ret = dtpm_subsys[i]->setup(parent, np);
		if (ret) {
			pr_err("Failed to setup '%s': %d\n", dtpm_subsys[i]->name, ret);
			of_node_put(np);
			return ERR_PTR(ret);
		}
	}

	of_node_put(np);

	/*
	 * By returning a NULL pointer, we let know the caller there
	 * is no child for us as we are a leaf of the tree
	 */
	return NULL;
}

typedef struct dtpm * (*dtpm_node_callback_t)(const struct dtpm_node *, struct dtpm *);

static dtpm_node_callback_t dtpm_node_callback[] = {
	[DTPM_NODE_VIRTUAL] = dtpm_setup_virtual,
	[DTPM_NODE_DT] = dtpm_setup_dt,
};

static int dtpm_for_each_child(const struct dtpm_node *hierarchy,
			       const struct dtpm_node *it, struct dtpm *parent)
{
	struct dtpm *dtpm;
	int i, ret;

	for (i = 0; hierarchy[i].name; i++) {

		if (hierarchy[i].parent != it)
			continue;

		dtpm = dtpm_node_callback[hierarchy[i].type](&hierarchy[i], parent);

		/*
		 * A NULL pointer means there is no children, hence we
		 * continue without going deeper in the recursivity.
		 */
		if (!dtpm)
			continue;

		/*
		 * There are multiple reasons why the callback could
		 * fail. The generic glue is abstracting the backend
		 * and therefore it is not possible to report back or
		 * take a decision based on the error.  In any case,
		 * if this call fails, it is not critical in the
		 * hierarchy creation, we can assume the underlying
		 * service is not found, so we continue without this
		 * branch in the tree but with a warning to log the
		 * information the node was not created.
		 */
		if (IS_ERR(dtpm)) {
			pr_warn("Failed to create '%s' in the hierarchy\n",
				hierarchy[i].name);
			continue;
		}

		ret = dtpm_for_each_child(hierarchy, &hierarchy[i], dtpm);
		if (ret)
			return ret;
	}

	return 0;
}

static int __init init_dtpm(void)
/**
 * dtpm_create_hierarchy - Create the dtpm hierarchy
 * @hierarchy: An array of struct dtpm_node describing the hierarchy
 *
 * The function is called by the platform specific code with the
 * description of the different node in the hierarchy. It creates the
 * tree in the sysfs filesystem under the powercap dtpm entry.
 *
 * The expected tree has the format:
 *
 * struct dtpm_node hierarchy[] = {
 *	[0] { .name = "topmost", type =  DTPM_NODE_VIRTUAL },
 *	[1] { .name = "package", .type = DTPM_NODE_VIRTUAL, .parent = &hierarchy[0] },
 *	[2] { .name = "/cpus/cpu0", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
 *	[3] { .name = "/cpus/cpu1", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
 *	[4] { .name = "/cpus/cpu2", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
 *	[5] { .name = "/cpus/cpu3", .type = DTPM_NODE_DT, .parent = &hierarchy[1] },
 *	[6] { }
 * };
 *
 * The last element is always an empty one and marks the end of the
 * array.
 *
 * Return: zero on success, a negative value in case of error. Errors
 * are reported back from the underlying functions.
 */
int dtpm_create_hierarchy(struct of_device_id *dtpm_match_table)
{
	const struct of_device_id *match;
	const struct dtpm_node *hierarchy;
	struct device_node *np;
	int i, ret;

	mutex_lock(&dtpm_lock);

	if (pct) {
		ret = -EBUSY;
		goto out_unlock;
	}

	pct = powercap_register_control_type(NULL, "dtpm", NULL);
	if (IS_ERR(pct)) {
		pr_err("Failed to register control type\n");
		return PTR_ERR(pct);
		ret = PTR_ERR(pct);
		goto out_pct;
	}

	ret = -ENODEV;
	np = of_find_node_by_path("/");
	if (!np)
		goto out_err;

	match = of_match_node(dtpm_match_table, np);

	of_node_put(np);

	if (!match)
		goto out_err;

	hierarchy = match->data;
	if (!hierarchy) {
		ret = -EFAULT;
		goto out_err;
	}

	ret = dtpm_for_each_child(hierarchy, NULL, NULL);
	if (ret)
		goto out_err;
	
	for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {

		if (!dtpm_subsys[i]->init)
			continue;

		ret = dtpm_subsys[i]->init();
		if (ret)
			pr_info("Failed to initialize '%s': %d",
				dtpm_subsys[i]->name, ret);
	}

	mutex_unlock(&dtpm_lock);

	return 0;

out_err:
	powercap_unregister_control_type(pct);
out_pct:
	pct = NULL;
out_unlock:
	mutex_unlock(&dtpm_lock);
	
	return ret;
}
EXPORT_SYMBOL_GPL(dtpm_create_hierarchy);

static void __dtpm_destroy_hierarchy(struct dtpm *dtpm)
{
	struct dtpm *child, *aux;

	list_for_each_entry_safe(child, aux, &dtpm->children, sibling)
		__dtpm_destroy_hierarchy(child);

	/*
	 * At this point, we know all children were removed from the
	 * recursive call before
	 */
	dtpm_unregister(dtpm);
}

void dtpm_destroy_hierarchy(void)
{
	int i;

	mutex_lock(&dtpm_lock);

	if (!pct)
		goto out_unlock;

	__dtpm_destroy_hierarchy(root);
	

	for (i = 0; i < ARRAY_SIZE(dtpm_subsys); i++) {

		if (!dtpm_subsys[i]->exit)
			continue;

		dtpm_subsys[i]->exit();
	}

	powercap_unregister_control_type(pct);

	pct = NULL;

	root = NULL;

out_unlock:
	mutex_unlock(&dtpm_lock);
}
late_initcall(init_dtpm);
EXPORT_SYMBOL_GPL(dtpm_destroy_hierarchy);
+48 −7
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
#include <linux/cpuhotplug.h>
#include <linux/dtpm.h>
#include <linux/energy_model.h>
#include <linux/of.h>
#include <linux/pm_qos.h>
#include <linux/slab.h>
#include <linux/units.h>
@@ -150,10 +151,17 @@ static int update_pd_power_uw(struct dtpm *dtpm)
static void pd_release(struct dtpm *dtpm)
{
	struct dtpm_cpu *dtpm_cpu = to_dtpm_cpu(dtpm);
	struct cpufreq_policy *policy;

	if (freq_qos_request_active(&dtpm_cpu->qos_req))
		freq_qos_remove_request(&dtpm_cpu->qos_req);

	policy = cpufreq_cpu_get(dtpm_cpu->cpu);
	if (policy) {
		for_each_cpu(dtpm_cpu->cpu, policy->related_cpus)
			per_cpu(dtpm_per_cpu, dtpm_cpu->cpu) = NULL;
	}
	
	kfree(dtpm_cpu);
}

@@ -176,6 +184,17 @@ static int cpuhp_dtpm_cpu_offline(unsigned int cpu)
}

static int cpuhp_dtpm_cpu_online(unsigned int cpu)
{
	struct dtpm_cpu *dtpm_cpu;

	dtpm_cpu = per_cpu(dtpm_per_cpu, cpu);
	if (dtpm_cpu)
		return dtpm_update_power(&dtpm_cpu->dtpm);

	return 0;
}

static int __dtpm_cpu_setup(int cpu, struct dtpm *parent)
{
	struct dtpm_cpu *dtpm_cpu;
	struct cpufreq_policy *policy;
@@ -183,6 +202,10 @@ static int cpuhp_dtpm_cpu_online(unsigned int cpu)
	char name[CPUFREQ_NAME_LEN];
	int ret = -ENOMEM;

	dtpm_cpu = per_cpu(dtpm_per_cpu, cpu);
	if (dtpm_cpu)
		return 0;

	policy = cpufreq_cpu_get(cpu);
	if (!policy)
		return 0;
@@ -191,10 +214,6 @@ static int cpuhp_dtpm_cpu_online(unsigned int cpu)
	if (!pd)
		return -EINVAL;

	dtpm_cpu = per_cpu(dtpm_per_cpu, cpu);
	if (dtpm_cpu)
		return dtpm_update_power(&dtpm_cpu->dtpm);

	dtpm_cpu = kzalloc(sizeof(*dtpm_cpu), GFP_KERNEL);
	if (!dtpm_cpu)
		return -ENOMEM;
@@ -207,7 +226,7 @@ static int cpuhp_dtpm_cpu_online(unsigned int cpu)

	snprintf(name, sizeof(name), "cpu%d-cpufreq", dtpm_cpu->cpu);

	ret = dtpm_register(name, &dtpm_cpu->dtpm, NULL);
	ret = dtpm_register(name, &dtpm_cpu->dtpm, parent);
	if (ret)
		goto out_kfree_dtpm_cpu;

@@ -231,7 +250,18 @@ static int cpuhp_dtpm_cpu_online(unsigned int cpu)
	return ret;
}

static int __init dtpm_cpu_init(void)
static int dtpm_cpu_setup(struct dtpm *dtpm, struct device_node *np)
{
	int cpu;

	cpu = of_cpu_node_to_id(np);
	if (cpu < 0)
		return 0;

	return __dtpm_cpu_setup(cpu, dtpm);
}

static int dtpm_cpu_init(void)
{
	int ret;

@@ -269,4 +299,15 @@ static int __init dtpm_cpu_init(void)
	return 0;
}

DTPM_DECLARE(dtpm_cpu, dtpm_cpu_init);
static void dtpm_cpu_exit(void)
{
	cpuhp_remove_state_nocalls(CPUHP_AP_ONLINE_DYN);
	cpuhp_remove_state_nocalls(CPUHP_AP_DTPM_CPU_DEAD);
}

struct dtpm_subsys_ops dtpm_cpu_ops = {
	.name = KBUILD_MODNAME,
	.init = dtpm_cpu_init,
	.exit = dtpm_cpu_exit,
	.setup = dtpm_cpu_setup,
};
+203 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright 2021 Linaro Limited
 *
 * Author: Daniel Lezcano <daniel.lezcano@linaro.org>
 *
 * The devfreq device combined with the energy model and the load can
 * give an estimation of the power consumption as well as limiting the
 * power.
 *
 */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/cpumask.h>
#include <linux/devfreq.h>
#include <linux/dtpm.h>
#include <linux/energy_model.h>
#include <linux/of.h>
#include <linux/pm_qos.h>
#include <linux/slab.h>
#include <linux/units.h>

struct dtpm_devfreq {
	struct dtpm dtpm;
	struct dev_pm_qos_request qos_req;
	struct devfreq *devfreq;
};

static struct dtpm_devfreq *to_dtpm_devfreq(struct dtpm *dtpm)
{
	return container_of(dtpm, struct dtpm_devfreq, dtpm);
}

static int update_pd_power_uw(struct dtpm *dtpm)
{
	struct dtpm_devfreq *dtpm_devfreq = to_dtpm_devfreq(dtpm);
	struct devfreq *devfreq = dtpm_devfreq->devfreq;
	struct device *dev = devfreq->dev.parent;
	struct em_perf_domain *pd = em_pd_get(dev);

	dtpm->power_min = pd->table[0].power;
	dtpm->power_min *= MICROWATT_PER_MILLIWATT;

	dtpm->power_max = pd->table[pd->nr_perf_states - 1].power;
	dtpm->power_max *= MICROWATT_PER_MILLIWATT;

	return 0;
}

static u64 set_pd_power_limit(struct dtpm *dtpm, u64 power_limit)
{
	struct dtpm_devfreq *dtpm_devfreq = to_dtpm_devfreq(dtpm);
	struct devfreq *devfreq = dtpm_devfreq->devfreq;
	struct device *dev = devfreq->dev.parent;
	struct em_perf_domain *pd = em_pd_get(dev);
	unsigned long freq;
	u64 power;
	int i;

	for (i = 0; i < pd->nr_perf_states; i++) {

		power = pd->table[i].power * MICROWATT_PER_MILLIWATT;
		if (power > power_limit)
			break;
	}

	freq = pd->table[i - 1].frequency;

	dev_pm_qos_update_request(&dtpm_devfreq->qos_req, freq);

	power_limit = pd->table[i - 1].power * MICROWATT_PER_MILLIWATT;

	return power_limit;
}

static void _normalize_load(struct devfreq_dev_status *status)
{
	if (status->total_time > 0xfffff) {
		status->total_time >>= 10;
		status->busy_time >>= 10;
	}

	status->busy_time <<= 10;
	status->busy_time /= status->total_time ? : 1;

	status->busy_time = status->busy_time ? : 1;
	status->total_time = 1024;
}

static u64 get_pd_power_uw(struct dtpm *dtpm)
{
	struct dtpm_devfreq *dtpm_devfreq = to_dtpm_devfreq(dtpm);
	struct devfreq *devfreq = dtpm_devfreq->devfreq;
	struct device *dev = devfreq->dev.parent;
	struct em_perf_domain *pd = em_pd_get(dev);
	struct devfreq_dev_status status;
	unsigned long freq;
	u64 power;
	int i;

	mutex_lock(&devfreq->lock);
	status = devfreq->last_status;
	mutex_unlock(&devfreq->lock);

	freq = DIV_ROUND_UP(status.current_frequency, HZ_PER_KHZ);
	_normalize_load(&status);

	for (i = 0; i < pd->nr_perf_states; i++) {

		if (pd->table[i].frequency < freq)
			continue;

		power = pd->table[i].power * MICROWATT_PER_MILLIWATT;
		power *= status.busy_time;
		power >>= 10;

		return power;
	}

	return 0;
}

static void pd_release(struct dtpm *dtpm)
{
	struct dtpm_devfreq *dtpm_devfreq = to_dtpm_devfreq(dtpm);

	if (dev_pm_qos_request_active(&dtpm_devfreq->qos_req))
		dev_pm_qos_remove_request(&dtpm_devfreq->qos_req);

	kfree(dtpm_devfreq);
}

static struct dtpm_ops dtpm_ops = {
	.set_power_uw = set_pd_power_limit,
	.get_power_uw = get_pd_power_uw,
	.update_power_uw = update_pd_power_uw,
	.release = pd_release,
};

static int __dtpm_devfreq_setup(struct devfreq *devfreq, struct dtpm *parent)
{
	struct device *dev = devfreq->dev.parent;
	struct dtpm_devfreq *dtpm_devfreq;
	struct em_perf_domain *pd;
	int ret = -ENOMEM;

	pd = em_pd_get(dev);
	if (!pd) {
		ret = dev_pm_opp_of_register_em(dev, NULL);
		if (ret) {
			pr_err("No energy model available for '%s'\n", dev_name(dev));
			return -EINVAL;
		}
	}

	dtpm_devfreq = kzalloc(sizeof(*dtpm_devfreq), GFP_KERNEL);
	if (!dtpm_devfreq)
		return -ENOMEM;

	dtpm_init(&dtpm_devfreq->dtpm, &dtpm_ops);

	dtpm_devfreq->devfreq = devfreq;

	ret = dtpm_register(dev_name(dev), &dtpm_devfreq->dtpm, parent);
	if (ret) {
		pr_err("Failed to register '%s': %d\n", dev_name(dev), ret);
		kfree(dtpm_devfreq);
		return ret;
	}

	ret = dev_pm_qos_add_request(dev, &dtpm_devfreq->qos_req,
				     DEV_PM_QOS_MAX_FREQUENCY,
				     PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE);
	if (ret) {
		pr_err("Failed to add QoS request: %d\n", ret);
		goto out_dtpm_unregister;
	}

	dtpm_update_power(&dtpm_devfreq->dtpm);

	return 0;

out_dtpm_unregister:
	dtpm_unregister(&dtpm_devfreq->dtpm);

	return ret;
}

static int dtpm_devfreq_setup(struct dtpm *dtpm, struct device_node *np)
{
	struct devfreq *devfreq;

	devfreq = devfreq_get_devfreq_by_node(np);
	if (IS_ERR(devfreq))
		return 0;

	return __dtpm_devfreq_setup(devfreq, dtpm);
}

struct dtpm_subsys_ops dtpm_devfreq_ops = {
	.name = KBUILD_MODNAME,
	.setup = dtpm_devfreq_setup,
};
Loading