[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <dbd33da2-ca46-b51e-0044-b1efebf7480d@codeaurora.org>
Date: Thu, 31 May 2018 14:53:02 -0700
From: Saravana Kannan <skannan@...eaurora.org>
To: MyungJoo Ham <myungjoo.ham@...sung.com>,
Kyungmin Park <kyungmin.park@...sung.com>,
Chanwoo Choi <cw00.choi@...sung.com>,
Rob Herring <robh+dt@...nel.org>,
Mark Rutland <mark.rutland@....com>
Cc: Rajendra Nayak <rjendra@...eaurora.org>,
Amit Kucheria <amit.kucheria@...aro.org>,
linux-pm@...r.kernel.org, devicetree@...r.kernel.org,
linux-kernel@...r.kernel.org
Subject: Re: [PATCH 2/2] PM / devfreq: Generic cpufreq governor
Chanwoo,
Friendly reminder for a review. I've addressed your comments on v1 of
this patch.
-Saravana
On 05/18/2018 01:24 AM, Saravana Kannan wrote:
> This devfreq governor is a generic implementation that can scale any
> devfreq device based on the current CPU frequency of all ONLINE CPUs. It
> allows for specifying CPU freq to devfreq mapping for specific devices.
> When such a mapping is not present, it defaults to scaling the device
> frequency in proportion to the CPU frequency.
>
> Change-Id: I7f786b9059435afe85b9ec8c504a4655731ee20e
> Signed-off-by: Saravana Kannan <skannan@...eaurora.org>
> ---
> .../bindings/devfreq/devfreq-cpufreq.txt | 53 ++
> drivers/devfreq/Kconfig | 8 +
> drivers/devfreq/Makefile | 1 +
> drivers/devfreq/governor_cpufreq.c | 628 +++++++++++++++++++++
> 4 files changed, 690 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/devfreq/devfreq-cpufreq.txt
> create mode 100644 drivers/devfreq/governor_cpufreq.c
>
> diff --git a/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq.txt b/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq.txt
> new file mode 100644
> index 0000000..6537538
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/devfreq/devfreq-cpufreq.txt
> @@ -0,0 +1,53 @@
> +Devfreq CPUfreq governor
> +
> +devfreq-cpufreq is a parent device that contains one or more child devices.
> +Each child device provides CPU frequency to device frequency mapping for a
> +specific device. Examples of devices that could use this are: DDR, cache and
> +CCI.
> +
> +Parent device name shall be "devfreq-cpufreq".
> +
> +Required child device properties:
> +- cpu-to-dev-map, or cpu-to-dev-map-<X>:
> + A list of tuples where each tuple consists of a
> + CPU frequency (KHz) and the corresponding device
> + frequency. CPU frequencies not listed in the table
> + will use the device frequency that corresponds to the
> + next rounded up CPU frequency.
> + Use "cpu-to-dev-map" if all CPUs in the system should
> + share same mapping.
> + Use cpu-to-dev-map-<cpuid> to describe different
> + mappings for different CPUs. The property should be
> + listed only for the first CPU if multiple CPUs are
> + synchronous.
> +- target-dev: Phandle to device that this mapping applies to.
> +
> +Example:
> + devfreq-cpufreq {
> + cpubw-cpufreq {
> + target-dev = <&cpubw>;
> + cpu-to-dev-map =
> + < 300000 1144 >,
> + < 422400 2288 >,
> + < 652800 3051 >,
> + < 883200 5996 >,
> + < 1190400 8056 >,
> + < 1497600 10101 >,
> + < 1728000 12145 >,
> + < 2649600 16250 >;
> + };
> +
> + cache-cpufreq {
> + target-dev = <&cache>;
> + cpu-to-dev-map =
> + < 300000 300000 >,
> + < 422400 422400 >,
> + < 652800 499200 >,
> + < 883200 576000 >,
> + < 960000 960000 >,
> + < 1497600 1036800 >,
> + < 1574400 1574400 >,
> + < 1728000 1651200 >,
> + < 2649600 1728000 >;
> + };
> + };
> diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig
> index 8503018..5eeb33f 100644
> --- a/drivers/devfreq/Kconfig
> +++ b/drivers/devfreq/Kconfig
> @@ -73,6 +73,14 @@ config DEVFREQ_GOV_PASSIVE
> through sysfs entries. The passive governor recommends that
> devfreq device uses the OPP table to get the frequency/voltage.
>
> +config DEVFREQ_GOV_CPUFREQ
> + tristate "CPUfreq"
> + depends on CPU_FREQ
> + help
> + Chooses frequency based on the online CPUs' current frequency and a
> + CPU frequency to device frequency mapping table(s). This governor
> + can be useful for controlling devices such as DDR, cache, CCI, etc.
> +
> comment "DEVFREQ Drivers"
>
> config ARM_EXYNOS_BUS_DEVFREQ
> diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile
> index f1cc8990..cafe7c2 100644
> --- a/drivers/devfreq/Makefile
> +++ b/drivers/devfreq/Makefile
> @@ -6,6 +6,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o
> obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o
> obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o
> obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o
> +obj-$(CONFIG_DEVFREQ_GOV_CPUFREQ) += governor_cpufreq.o
>
> # DEVFREQ Drivers
> obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o
> diff --git a/drivers/devfreq/governor_cpufreq.c b/drivers/devfreq/governor_cpufreq.c
> new file mode 100644
> index 0000000..8f8ea26
> --- /dev/null
> +++ b/drivers/devfreq/governor_cpufreq.c
> @@ -0,0 +1,628 @@
> +/*
> + * Copyright (c) 2014-2015, 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) "dev-cpufreq: " fmt
> +
> +#include <linux/devfreq.h>
> +#include <linux/cpu.h>
> +#include <linux/cpufreq.h>
> +#include <linux/cpumask.h>
> +#include <linux/slab.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +#include <linux/module.h>
> +#include "governor.h"
> +
> +struct cpu_state {
> + unsigned int freq;
> + unsigned int min_freq;
> + unsigned int max_freq;
> + bool on;
> + unsigned int first_cpu;
> +};
> +static struct cpu_state *state[NR_CPUS];
> +static int cpufreq_cnt;
> +
> +struct freq_map {
> + unsigned int cpu_khz;
> + unsigned int target_freq;
> +};
> +
> +struct devfreq_node {
> + struct devfreq *df;
> + void *orig_data;
> + struct device *dev;
> + struct device_node *of_node;
> + struct list_head list;
> + struct freq_map **map;
> + struct freq_map *common_map;
> +};
> +static LIST_HEAD(devfreq_list);
> +static DEFINE_MUTEX(state_lock);
> +static DEFINE_MUTEX(cpufreq_reg_lock);
> +
> +static void update_all_devfreqs(void)
> +{
> + struct devfreq_node *node;
> +
> + list_for_each_entry(node, &devfreq_list, list) {
> + struct devfreq *df = node->df;
> + if (!node->df)
> + continue;
> + mutex_lock(&df->lock);
> + update_devfreq(df);
> + mutex_unlock(&df->lock);
> +
> + }
> +}
> +
> +static struct devfreq_node *find_devfreq_node(struct device *dev)
> +{
> + struct devfreq_node *node;
> +
> + list_for_each_entry(node, &devfreq_list, list)
> + if (node->dev == dev || node->of_node == dev->of_node)
> + return node;
> +
> + return NULL;
> +}
> +
> +/* ==================== cpufreq part ==================== */
> +static void add_policy(struct cpufreq_policy *policy)
> +{
> + struct cpu_state *new_state;
> + unsigned int cpu, first_cpu;
> +
> + if (state[policy->cpu]) {
> + state[policy->cpu]->freq = policy->cur;
> + state[policy->cpu]->on = true;
> + } else {
> + new_state = kzalloc(sizeof(struct cpu_state), GFP_KERNEL);
> + if (!new_state)
> + return;
> +
> + first_cpu = cpumask_first(policy->related_cpus);
> + new_state->first_cpu = first_cpu;
> + new_state->freq = policy->cur;
> + new_state->min_freq = policy->cpuinfo.min_freq;
> + new_state->max_freq = policy->cpuinfo.max_freq;
> + new_state->on = true;
> +
> + for_each_cpu(cpu, policy->related_cpus)
> + state[cpu] = new_state;
> + }
> +}
> +
> +static int cpufreq_policy_notifier(struct notifier_block *nb,
> + unsigned long event, void *data)
> +{
> + struct cpufreq_policy *policy = data;
> +
> + switch (event) {
> + case CPUFREQ_CREATE_POLICY:
> + mutex_lock(&state_lock);
> + add_policy(policy);
> + update_all_devfreqs();
> + mutex_unlock(&state_lock);
> + break;
> +
> + case CPUFREQ_REMOVE_POLICY:
> + mutex_lock(&state_lock);
> + if (state[policy->cpu]) {
> + state[policy->cpu]->on = false;
> + update_all_devfreqs();
> + }
> + mutex_unlock(&state_lock);
> + break;
> + }
> +
> + return 0;
> +}
> +
> +static struct notifier_block cpufreq_policy_nb = {
> + .notifier_call = cpufreq_policy_notifier
> +};
> +
> +static int cpufreq_trans_notifier(struct notifier_block *nb,
> + unsigned long event, void *data)
> +{
> + struct cpufreq_freqs *freq = data;
> + struct cpu_state *s;
> +
> + if (event != CPUFREQ_POSTCHANGE)
> + return 0;
> +
> + mutex_lock(&state_lock);
> +
> + s = state[freq->cpu];
> + if (!s)
> + goto out;
> +
> + if (s->freq != freq->new) {
> + s->freq = freq->new;
> + update_all_devfreqs();
> + }
> +
> +out:
> + mutex_unlock(&state_lock);
> + return 0;
> +}
> +
> +static struct notifier_block cpufreq_trans_nb = {
> + .notifier_call = cpufreq_trans_notifier
> +};
> +
> +static int register_cpufreq(void)
> +{
> + int ret = 0;
> + unsigned int cpu;
> + struct cpufreq_policy *policy;
> +
> + mutex_lock(&cpufreq_reg_lock);
> +
> + if (cpufreq_cnt)
> + goto cnt_not_zero;
> +
> + get_online_cpus();
> + ret = cpufreq_register_notifier(&cpufreq_policy_nb,
> + CPUFREQ_POLICY_NOTIFIER);
> + if (ret)
> + goto out;
> +
> + ret = cpufreq_register_notifier(&cpufreq_trans_nb,
> + CPUFREQ_TRANSITION_NOTIFIER);
> + if (ret) {
> + cpufreq_unregister_notifier(&cpufreq_policy_nb,
> + CPUFREQ_POLICY_NOTIFIER);
> + goto out;
> + }
> +
> + for_each_online_cpu(cpu) {
> + policy = cpufreq_cpu_get(cpu);
> + if (policy) {
> + add_policy(policy);
> + cpufreq_cpu_put(policy);
> + }
> + }
> +out:
> + put_online_cpus();
> +cnt_not_zero:
> + if (!ret)
> + cpufreq_cnt++;
> + mutex_unlock(&cpufreq_reg_lock);
> + return ret;
> +}
> +
> +static int unregister_cpufreq(void)
> +{
> + int ret = 0;
> + int cpu;
> +
> + mutex_lock(&cpufreq_reg_lock);
> +
> + if (cpufreq_cnt > 1)
> + goto out;
> +
> + cpufreq_unregister_notifier(&cpufreq_policy_nb,
> + CPUFREQ_POLICY_NOTIFIER);
> + cpufreq_unregister_notifier(&cpufreq_trans_nb,
> + CPUFREQ_TRANSITION_NOTIFIER);
> +
> + for (cpu = ARRAY_SIZE(state) - 1; cpu >= 0; cpu--) {
> + if (!state[cpu])
> + continue;
> + if (state[cpu]->first_cpu == cpu)
> + kfree(state[cpu]);
> + state[cpu] = NULL;
> + }
> +
> +out:
> + cpufreq_cnt--;
> + mutex_unlock(&cpufreq_reg_lock);
> + return ret;
> +}
> +
> +/* ==================== devfreq part ==================== */
> +
> +static unsigned int interpolate_freq(struct devfreq *df, unsigned int cpu)
> +{
> + unsigned long *freq_table = df->profile->freq_table;
> + unsigned int cpu_min = state[cpu]->min_freq;
> + unsigned int cpu_max = state[cpu]->max_freq;
> + unsigned int cpu_freq = state[cpu]->freq;
> + unsigned int dev_min, dev_max, cpu_percent;
> +
> + if (freq_table) {
> + dev_min = freq_table[0];
> + dev_max = freq_table[df->profile->max_state - 1];
> + } else {
> + if (df->max_freq <= df->min_freq)
> + return 0;
> + dev_min = df->min_freq;
> + dev_max = df->max_freq;
> + }
> +
> + cpu_percent = ((cpu_freq - cpu_min) * 100) / (cpu_max - cpu_min);
> + return dev_min + mult_frac(dev_max - dev_min, cpu_percent, 100);
> +}
> +
> +static unsigned int cpu_to_dev_freq(struct devfreq *df, unsigned int cpu)
> +{
> + struct freq_map *map = NULL;
> + unsigned int cpu_khz = 0, freq;
> + struct devfreq_node *n = df->data;
> +
> + if (!state[cpu] || !state[cpu]->on || state[cpu]->first_cpu != cpu) {
> + freq = 0;
> + goto out;
> + }
> +
> + if (n->common_map)
> + map = n->common_map;
> + else if (n->map)
> + map = n->map[cpu];
> +
> + cpu_khz = state[cpu]->freq;
> +
> + if (!map) {
> + freq = interpolate_freq(df, cpu);
> + goto out;
> + }
> +
> + while (map->cpu_khz && map->cpu_khz < cpu_khz)
> + map++;
> + if (!map->cpu_khz)
> + map--;
> + freq = map->target_freq;
> +
> +out:
> + dev_dbg(df->dev.parent, "CPU%u: %d -> dev: %u\n", cpu, cpu_khz, freq);
> + return freq;
> +}
> +
> +static int devfreq_cpufreq_get_freq(struct devfreq *df,
> + unsigned long *freq)
> +{
> + unsigned int cpu, tgt_freq = 0;
> + struct devfreq_node *node;
> +
> + node = df->data;
> + if (!node) {
> + pr_err("Unable to find devfreq node!\n");
> + return -ENODEV;
> + }
> +
> + for_each_possible_cpu(cpu)
> + tgt_freq = max(tgt_freq, cpu_to_dev_freq(df, cpu));
> +
> + *freq = tgt_freq;
> + return 0;
> +}
> +
> +static unsigned int show_table(char *buf, unsigned int len,
> + struct freq_map *map)
> +{
> + unsigned int cnt = 0;
> +
> + cnt += snprintf(buf + cnt, len - cnt, "CPU freq\tDevice freq\n");
> +
> + while (map->cpu_khz && cnt < len) {
> + cnt += snprintf(buf + cnt, len - cnt, "%8u\t%11u\n",
> + map->cpu_khz, map->target_freq);
> + map++;
> + }
> + if (cnt < len)
> + cnt += snprintf(buf + cnt, len - cnt, "\n");
> +
> + return cnt;
> +}
> +
> +static ssize_t show_map(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct devfreq *df = to_devfreq(dev);
> + struct devfreq_node *n = df->data;
> + struct freq_map *map;
> + unsigned int cnt = 0, cpu;
> +
> + mutex_lock(&state_lock);
> + if (n->common_map) {
> + map = n->common_map;
> + cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
> + "Common table for all CPUs:\n");
> + cnt += show_table(buf + cnt, PAGE_SIZE - cnt, map);
> + } else if (n->map) {
> + for_each_possible_cpu(cpu) {
> + map = n->map[cpu];
> + if (!map)
> + continue;
> + cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
> + "CPU %u:\n", cpu);
> + if (cnt >= PAGE_SIZE)
> + break;
> + cnt += show_table(buf + cnt, PAGE_SIZE - cnt, map);
> + if (cnt >= PAGE_SIZE)
> + break;
> + }
> + } else {
> + cnt += snprintf(buf + cnt, PAGE_SIZE - cnt,
> + "Device freq interpolated based on CPU freq\n");
> + }
> + mutex_unlock(&state_lock);
> +
> + return cnt;
> +}
> +
> +static DEVICE_ATTR(freq_map, 0444, show_map, NULL);
> +static struct attribute *dev_attr[] = {
> + &dev_attr_freq_map.attr,
> + NULL,
> +};
> +
> +static struct attribute_group dev_attr_group = {
> + .name = "cpufreq",
> + .attrs = dev_attr,
> +};
> +
> +static int devfreq_cpufreq_gov_start(struct devfreq *devfreq)
> +{
> + int ret = 0;
> + struct devfreq_node *node;
> + bool alloc = false;
> +
> + ret = register_cpufreq();
> + if (ret)
> + return ret;
> +
> + ret = sysfs_create_group(&devfreq->dev.kobj, &dev_attr_group);
> + if (ret) {
> + unregister_cpufreq();
> + return ret;
> + }
> +
> + mutex_lock(&state_lock);
> +
> + node = find_devfreq_node(devfreq->dev.parent);
> + if (node == NULL) {
> + node = kzalloc(sizeof(struct devfreq_node), GFP_KERNEL);
> + if (!node) {
> + pr_err("Out of memory!\n");
> + ret = -ENOMEM;
> + goto alloc_fail;
> + }
> + alloc = true;
> + node->dev = devfreq->dev.parent;
> + list_add_tail(&node->list, &devfreq_list);
> + }
> + node->df = devfreq;
> + node->orig_data = devfreq->data;
> + devfreq->data = node;
> +
> + mutex_lock(&devfreq->lock);
> + ret = update_devfreq(devfreq);
> + mutex_unlock(&devfreq->lock);
> + if (ret) {
> + pr_err("Freq update failed!\n");
> + goto update_fail;
> + }
> +
> + mutex_unlock(&state_lock);
> + return 0;
> +
> +update_fail:
> + devfreq->data = node->orig_data;
> + if (alloc) {
> + list_del(&node->list);
> + kfree(node);
> + }
> +alloc_fail:
> + mutex_unlock(&state_lock);
> + sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group);
> + unregister_cpufreq();
> + return ret;
> +}
> +
> +static void devfreq_cpufreq_gov_stop(struct devfreq *devfreq)
> +{
> + struct devfreq_node *node = devfreq->data;
> +
> + mutex_lock(&state_lock);
> + devfreq->data = node->orig_data;
> + if (node->map || node->common_map) {
> + node->df = NULL;
> + } else {
> + list_del(&node->list);
> + kfree(node);
> + }
> + mutex_unlock(&state_lock);
> +
> + sysfs_remove_group(&devfreq->dev.kobj, &dev_attr_group);
> + unregister_cpufreq();
> +}
> +
> +static int devfreq_cpufreq_ev_handler(struct devfreq *devfreq,
> + unsigned int event, void *data)
> +{
> + int ret;
> +
> + switch (event) {
> + case DEVFREQ_GOV_START:
> +
> + ret = devfreq_cpufreq_gov_start(devfreq);
> + if (ret) {
> + pr_err("Governor start failed!\n");
> + return ret;
> + }
> + pr_debug("Enabled dev CPUfreq governor\n");
> + break;
> +
> + case DEVFREQ_GOV_STOP:
> +
> + devfreq_cpufreq_gov_stop(devfreq);
> + pr_debug("Disabled dev CPUfreq governor\n");
> + break;
> + }
> +
> + return 0;
> +}
> +
> +static struct devfreq_governor devfreq_cpufreq = {
> + .name = "cpufreq",
> + .get_target_freq = devfreq_cpufreq_get_freq,
> + .event_handler = devfreq_cpufreq_ev_handler,
> +};
> +
> +#define NUM_COLS 2
> +static struct freq_map *read_tbl(struct device_node *of_node, char *prop_name)
> +{
> + int len, nf, i, j;
> + u32 data;
> + struct freq_map *tbl;
> +
> + if (!of_find_property(of_node, prop_name, &len))
> + return NULL;
> + len /= sizeof(data);
> +
> + if (len % NUM_COLS || len == 0)
> + return NULL;
> + nf = len / NUM_COLS;
> +
> + tbl = kzalloc((nf + 1) * sizeof(*tbl), GFP_KERNEL);
> + if (!tbl)
> + return NULL;
> +
> + for (i = 0, j = 0; i < nf; i++, j += 2) {
> + of_property_read_u32_index(of_node, prop_name, j, &data);
> + tbl[i].cpu_khz = data;
> +
> + of_property_read_u32_index(of_node, prop_name, j + 1, &data);
> + tbl[i].target_freq = data;
> + }
> + tbl[i].cpu_khz = 0;
> +
> + return tbl;
> +}
> +
> +#define PROP_TARGET "target-dev"
> +#define PROP_TABLE "cpu-to-dev-map"
> +static int add_table_from_of(struct device_node *of_node)
> +{
> + struct device_node *target_of_node;
> + struct devfreq_node *node;
> + struct freq_map *common_tbl;
> + struct freq_map **tbl_list = NULL;
> + static char prop_name[] = PROP_TABLE "-999999";
> + int cpu, ret, cnt = 0, prop_sz = ARRAY_SIZE(prop_name);
> +
> + target_of_node = of_parse_phandle(of_node, PROP_TARGET, 0);
> + if (!target_of_node)
> + return -EINVAL;
> +
> + node = kzalloc(sizeof(struct devfreq_node), GFP_KERNEL);
> + if (!node)
> + return -ENOMEM;
> +
> + common_tbl = read_tbl(of_node, PROP_TABLE);
> + if (!common_tbl) {
> + tbl_list = kzalloc(sizeof(*tbl_list) * NR_CPUS, GFP_KERNEL);
> + if (!tbl_list) {
> + ret = -ENOMEM;
> + goto err_list;
> + }
> +
> + for_each_possible_cpu(cpu) {
> + ret = snprintf(prop_name, prop_sz, "%s-%d",
> + PROP_TABLE, cpu);
> + if (ret >= prop_sz) {
> + pr_warn("More CPUs than I can handle!\n");
> + pr_warn("Skipping rest of the tables!\n");
> + break;
> + }
> + tbl_list[cpu] = read_tbl(of_node, prop_name);
> + if (tbl_list[cpu])
> + cnt++;
> + }
> + }
> + if (!common_tbl && !cnt) {
> + ret = -EINVAL;
> + goto err_tbl;
> + }
> +
> + mutex_lock(&state_lock);
> + node->of_node = target_of_node;
> + node->map = tbl_list;
> + node->common_map = common_tbl;
> + list_add_tail(&node->list, &devfreq_list);
> + mutex_unlock(&state_lock);
> +
> + return 0;
> +err_tbl:
> + kfree(tbl_list);
> +err_list:
> + kfree(node);
> + return ret;
> +}
> +
> +static int __init devfreq_cpufreq_init(void)
> +{
> + int ret;
> + struct device_node *of_par, *of_child;
> +
> + of_par = of_find_node_by_name(NULL, "devfreq-cpufreq");
> + if (of_par) {
> + for_each_child_of_node(of_par, of_child) {
> + ret = add_table_from_of(of_child);
> + if (ret)
> + pr_err("Parsing %s failed!\n", of_child->name);
> + else
> + pr_debug("Parsed %s.\n", of_child->name);
> + }
> + of_node_put(of_par);
> + } else {
> + pr_info("No tables parsed from DT.\n");
> + }
> +
> + ret = devfreq_add_governor(&devfreq_cpufreq);
> + if (ret) {
> + pr_err("Governor add failed!\n");
> + return ret;
> + }
> +
> + return 0;
> +}
> +subsys_initcall(devfreq_cpufreq_init);
> +
> +static void __exit devfreq_cpufreq_exit(void)
> +{
> + int ret, cpu;
> + struct devfreq_node *node, *tmp;
> +
> + ret = devfreq_remove_governor(&devfreq_cpufreq);
> + if (ret)
> + pr_err("Governor remove failed!\n");
> +
> + mutex_lock(&state_lock);
> + list_for_each_entry_safe(node, tmp, &devfreq_list, list) {
> + kfree(node->common_map);
> + for_each_possible_cpu(cpu)
> + kfree(node->map[cpu]);
> + kfree(node->map);
> + list_del(&node->list);
> + kfree(node);
> + }
> + mutex_unlock(&state_lock);
> +}
> +module_exit(devfreq_cpufreq_exit);
> +
> +MODULE_DESCRIPTION("CPU freq based generic governor for devfreq devices");
> +MODULE_LICENSE("GPL v2");
--
Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum,
a Linux Foundation Collaborative Project
Powered by blists - more mailing lists