aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/ipq806x
diff options
context:
space:
mode:
authorChristian Marangi <ansuelsmth@gmail.com>2022-06-16 22:16:04 +0200
committerChristian Marangi <ansuelsmth@gmail.com>2022-10-11 21:28:42 +0200
commit3a911b8c2d97293f04288775599843ab5cb77045 (patch)
treef9483effcc443546d136510f19f3b89d1f04cae7 /target/linux/ipq806x
parent5ad826f48a4f3c3e08bd1da8c1504a53edd281a0 (diff)
downloadupstream-3a911b8c2d97293f04288775599843ab5cb77045.tar.gz
upstream-3a911b8c2d97293f04288775599843ab5cb77045.tar.bz2
upstream-3a911b8c2d97293f04288775599843ab5cb77045.zip
ipq806x: 5.15: backport devfreq new cpufreq based PASSIVE governor
Backport devfreq new cpufreq based PASSIVE governor needed for devfreq based fab and cache scaling. Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
Diffstat (limited to 'target/linux/ipq806x')
-rw-r--r--target/linux/ipq806x/patches-5.15/111-v5.19-01-PM-devfreq-Export-devfreq_get_freq_range-symbol-with.patch113
-rw-r--r--target/linux/ipq806x/patches-5.15/111-v5.19-02-PM-devfreq-Add-cpu-based-scaling-support-to-passive-.patch461
-rw-r--r--target/linux/ipq806x/patches-5.15/111-v5.19-03-PM-devfreq-passive-Reduce-duplicate-code-when-passiv.patch110
-rw-r--r--target/linux/ipq806x/patches-5.15/111-v5.19-04-PM-devfreq-passive-Keep-cpufreq_policy-for-possible-.patch232
-rw-r--r--target/linux/ipq806x/patches-5.15/111-v5.19-05-PM-devfreq-passive-Return-non-error-when-not-support.patch31
-rw-r--r--target/linux/ipq806x/patches-5.15/112-v5.19-PM-devfreq-Fix-kernel-warning-with-cpufreq-passive-r.patch31
-rw-r--r--target/linux/ipq806x/patches-5.15/113-v5.19-01-PM-devfreq-Fix-cpufreq-passive-unregister-errorin.patch85
-rw-r--r--target/linux/ipq806x/patches-5.15/113-v5.19-02-PM-devfreq-Fix-kernel-panic-with-cpu-based-scaling-t.patch39
-rw-r--r--target/linux/ipq806x/patches-5.15/113-v5.19-03-PM-devfreq-Rework-freq_table-to-be-local-to-devfr.patch269
-rw-r--r--target/linux/ipq806x/patches-5.15/113-v5.19-04-PM-devfreq-Mute-warning-on-governor-PROBE_DEFER.patch28
10 files changed, 1399 insertions, 0 deletions
diff --git a/target/linux/ipq806x/patches-5.15/111-v5.19-01-PM-devfreq-Export-devfreq_get_freq_range-symbol-with.patch b/target/linux/ipq806x/patches-5.15/111-v5.19-01-PM-devfreq-Export-devfreq_get_freq_range-symbol-with.patch
new file mode 100644
index 0000000000..0442105d87
--- /dev/null
+++ b/target/linux/ipq806x/patches-5.15/111-v5.19-01-PM-devfreq-Export-devfreq_get_freq_range-symbol-with.patch
@@ -0,0 +1,113 @@
+From 713472e53e6e53c985e283782b0fd76b8ecfd47e Mon Sep 17 00:00:00 2001
+From: Chanwoo Choi <cw00.choi@samsung.com>
+Date: Mon, 1 Mar 2021 02:07:29 +0900
+Subject: [PATCH 1/5] PM / devfreq: Export devfreq_get_freq_range symbol within
+ devfreq
+
+In order to get frequency range within devfreq governors,
+export devfreq_get_freq_range symbol within devfreq.
+
+Reviewed-by: Matthias Kaehlcke <mka@chromium.org>
+Tested-by: Chen-Yu Tsai <wenst@chromium.org>
+Tested-by: Johnson Wang <johnson.wang@mediatek.com>
+Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
+---
+ drivers/devfreq/devfreq.c | 20 ++++++++++++--------
+ drivers/devfreq/governor.h | 2 ++
+ 2 files changed, 14 insertions(+), 8 deletions(-)
+
+--- a/drivers/devfreq/devfreq.c
++++ b/drivers/devfreq/devfreq.c
+@@ -112,16 +112,16 @@ static unsigned long find_available_max_
+ }
+
+ /**
+- * get_freq_range() - Get the current freq range
++ * devfreq_get_freq_range() - Get the current freq range
+ * @devfreq: the devfreq instance
+ * @min_freq: the min frequency
+ * @max_freq: the max frequency
+ *
+ * This takes into consideration all constraints.
+ */
+-static void get_freq_range(struct devfreq *devfreq,
+- unsigned long *min_freq,
+- unsigned long *max_freq)
++void devfreq_get_freq_range(struct devfreq *devfreq,
++ unsigned long *min_freq,
++ unsigned long *max_freq)
+ {
+ unsigned long *freq_table = devfreq->profile->freq_table;
+ s32 qos_min_freq, qos_max_freq;
+@@ -158,6 +158,7 @@ static void get_freq_range(struct devfre
+ if (*min_freq > *max_freq)
+ *min_freq = *max_freq;
+ }
++EXPORT_SYMBOL(devfreq_get_freq_range);
+
+ /**
+ * devfreq_get_freq_level() - Lookup freq_table for the frequency
+@@ -418,7 +419,7 @@ int devfreq_update_target(struct devfreq
+ err = devfreq->governor->get_target_freq(devfreq, &freq);
+ if (err)
+ return err;
+- get_freq_range(devfreq, &min_freq, &max_freq);
++ devfreq_get_freq_range(devfreq, &min_freq, &max_freq);
+
+ if (freq < min_freq) {
+ freq = min_freq;
+@@ -785,6 +786,7 @@ struct devfreq *devfreq_add_device(struc
+ {
+ struct devfreq *devfreq;
+ struct devfreq_governor *governor;
++ unsigned long min_freq, max_freq;
+ int err = 0;
+
+ if (!dev || !profile || !governor_name) {
+@@ -849,6 +851,8 @@ struct devfreq *devfreq_add_device(struc
+ goto err_dev;
+ }
+
++ devfreq_get_freq_range(devfreq, &min_freq, &max_freq);
++
+ devfreq->suspend_freq = dev_pm_opp_get_suspend_opp_freq(dev);
+ devfreq->opp_table = dev_pm_opp_get_opp_table(dev);
+ if (IS_ERR(devfreq->opp_table))
+@@ -1561,7 +1565,7 @@ static ssize_t min_freq_show(struct devi
+ unsigned long min_freq, max_freq;
+
+ mutex_lock(&df->lock);
+- get_freq_range(df, &min_freq, &max_freq);
++ devfreq_get_freq_range(df, &min_freq, &max_freq);
+ mutex_unlock(&df->lock);
+
+ return sprintf(buf, "%lu\n", min_freq);
+@@ -1615,7 +1619,7 @@ static ssize_t max_freq_show(struct devi
+ unsigned long min_freq, max_freq;
+
+ mutex_lock(&df->lock);
+- get_freq_range(df, &min_freq, &max_freq);
++ devfreq_get_freq_range(df, &min_freq, &max_freq);
+ mutex_unlock(&df->lock);
+
+ return sprintf(buf, "%lu\n", max_freq);
+@@ -1929,7 +1933,7 @@ static int devfreq_summary_show(struct s
+
+ mutex_lock(&devfreq->lock);
+ cur_freq = devfreq->previous_freq;
+- get_freq_range(devfreq, &min_freq, &max_freq);
++ devfreq_get_freq_range(devfreq, &min_freq, &max_freq);
+ timer = devfreq->profile->timer;
+
+ if (IS_SUPPORTED_ATTR(devfreq->governor->attrs, POLLING_INTERVAL))
+--- a/drivers/devfreq/governor.h
++++ b/drivers/devfreq/governor.h
+@@ -86,6 +86,8 @@ int devfreq_remove_governor(struct devfr
+
+ int devfreq_update_status(struct devfreq *devfreq, unsigned long freq);
+ int devfreq_update_target(struct devfreq *devfreq, unsigned long freq);
++void devfreq_get_freq_range(struct devfreq *devfreq, unsigned long *min_freq,
++ unsigned long *max_freq);
+
+ static inline int devfreq_update_stats(struct devfreq *df)
+ {
diff --git a/target/linux/ipq806x/patches-5.15/111-v5.19-02-PM-devfreq-Add-cpu-based-scaling-support-to-passive-.patch b/target/linux/ipq806x/patches-5.15/111-v5.19-02-PM-devfreq-Add-cpu-based-scaling-support-to-passive-.patch
new file mode 100644
index 0000000000..52f1200c31
--- /dev/null
+++ b/target/linux/ipq806x/patches-5.15/111-v5.19-02-PM-devfreq-Add-cpu-based-scaling-support-to-passive-.patch
@@ -0,0 +1,461 @@
+From a03dacb0316f74400846aaf144d6c73f4217ca08 Mon Sep 17 00:00:00 2001
+From: Saravana Kannan <skannan@codeaurora.org>
+Date: Tue, 2 Mar 2021 15:58:21 +0900
+Subject: [PATCH 2/5] PM / devfreq: Add cpu based scaling support to passive
+ governor
+
+Many CPU architectures have caches that can scale independent of the
+CPUs. Frequency scaling of the caches is necessary to make sure that the
+cache is not a performance bottleneck that leads to poor performance and
+power. The same idea applies for RAM/DDR.
+
+To achieve this, this patch adds support for cpu based scaling to the
+passive governor. This is accomplished by taking the current frequency
+of each CPU frequency domain and then adjust the frequency of the cache
+(or any devfreq device) based on the frequency of the CPUs. It listens
+to CPU frequency transition notifiers to keep itself up to date on the
+current CPU frequency.
+
+To decide the frequency of the device, the governor does one of the
+following:
+* Derives the optimal devfreq device opp from required-opps property of
+ the parent cpu opp_table.
+
+* Scales the device frequency in proportion to the CPU frequency. So, if
+ the CPUs are running at their max frequency, the device runs at its
+ max frequency. If the CPUs are running at their min frequency, the
+ device runs at its min frequency. It is interpolated for frequencies
+ in between.
+
+Tested-by: Chen-Yu Tsai <wenst@chromium.org>
+Tested-by: Johnson Wang <johnson.wang@mediatek.com>
+Signed-off-by: Saravana Kannan <skannan@codeaurora.org>
+[Sibi: Integrated cpu-freqmap governor into passive_governor]
+Signed-off-by: Sibi Sankar <sibis@codeaurora.org>
+[Chanwoo: Fix conflict with latest code and cleanup code]
+Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
+---
+ drivers/devfreq/governor.h | 22 +++
+ drivers/devfreq/governor_passive.c | 298 +++++++++++++++++++++++++++--
+ include/linux/devfreq.h | 17 +-
+ 3 files changed, 323 insertions(+), 14 deletions(-)
+
+--- a/drivers/devfreq/governor.h
++++ b/drivers/devfreq/governor.h
+@@ -48,6 +48,28 @@
+ #define DEVFREQ_GOV_ATTR_TIMER BIT(1)
+
+ /**
++ * struct devfreq_cpu_data - Hold the per-cpu data
++ * @dev: reference to cpu device.
++ * @first_cpu: the cpumask of the first cpu of a policy.
++ * @opp_table: reference to cpu opp table.
++ * @cur_freq: the current frequency of the cpu.
++ * @min_freq: the min frequency of the cpu.
++ * @max_freq: the max frequency of the cpu.
++ *
++ * This structure stores the required cpu_data of a cpu.
++ * This is auto-populated by the governor.
++ */
++struct devfreq_cpu_data {
++ struct device *dev;
++ unsigned int first_cpu;
++
++ struct opp_table *opp_table;
++ unsigned int cur_freq;
++ unsigned int min_freq;
++ unsigned int max_freq;
++};
++
++/**
+ * struct devfreq_governor - Devfreq policy governor
+ * @node: list node - contains registered devfreq governors
+ * @name: Governor's name
+--- a/drivers/devfreq/governor_passive.c
++++ b/drivers/devfreq/governor_passive.c
+@@ -8,11 +8,85 @@
+ */
+
+ #include <linux/module.h>
++#include <linux/cpu.h>
++#include <linux/cpufreq.h>
++#include <linux/cpumask.h>
++#include <linux/slab.h>
+ #include <linux/device.h>
+ #include <linux/devfreq.h>
+ #include "governor.h"
+
+-static int devfreq_passive_get_target_freq(struct devfreq *devfreq,
++#define HZ_PER_KHZ 1000
++
++static unsigned long get_target_freq_by_required_opp(struct device *p_dev,
++ struct opp_table *p_opp_table,
++ struct opp_table *opp_table,
++ unsigned long *freq)
++{
++ struct dev_pm_opp *opp = NULL, *p_opp = NULL;
++ unsigned long target_freq;
++
++ if (!p_dev || !p_opp_table || !opp_table || !freq)
++ return 0;
++
++ p_opp = devfreq_recommended_opp(p_dev, freq, 0);
++ if (IS_ERR(p_opp))
++ return 0;
++
++ opp = dev_pm_opp_xlate_required_opp(p_opp_table, opp_table, p_opp);
++ dev_pm_opp_put(p_opp);
++
++ if (IS_ERR(opp))
++ return 0;
++
++ target_freq = dev_pm_opp_get_freq(opp);
++ dev_pm_opp_put(opp);
++
++ return target_freq;
++}
++
++static int get_target_freq_with_cpufreq(struct devfreq *devfreq,
++ unsigned long *target_freq)
++{
++ struct devfreq_passive_data *p_data =
++ (struct devfreq_passive_data *)devfreq->data;
++ struct devfreq_cpu_data *parent_cpu_data;
++ unsigned long cpu, cpu_cur, cpu_min, cpu_max, cpu_percent;
++ unsigned long dev_min, dev_max;
++ unsigned long freq = 0;
++
++ for_each_online_cpu(cpu) {
++ parent_cpu_data = p_data->parent_cpu_data[cpu];
++ if (!parent_cpu_data || parent_cpu_data->first_cpu != cpu)
++ continue;
++
++ /* Get target freq via required opps */
++ cpu_cur = parent_cpu_data->cur_freq * HZ_PER_KHZ;
++ freq = get_target_freq_by_required_opp(parent_cpu_data->dev,
++ parent_cpu_data->opp_table,
++ devfreq->opp_table, &cpu_cur);
++ if (freq) {
++ *target_freq = max(freq, *target_freq);
++ continue;
++ }
++
++ /* Use interpolation if required opps is not available */
++ devfreq_get_freq_range(devfreq, &dev_min, &dev_max);
++
++ cpu_min = parent_cpu_data->min_freq;
++ cpu_max = parent_cpu_data->max_freq;
++ cpu_cur = parent_cpu_data->cur_freq;
++
++ cpu_percent = ((cpu_cur - cpu_min) * 100) / (cpu_max - cpu_min);
++ freq = dev_min + mult_frac(dev_max - dev_min, cpu_percent, 100);
++
++ *target_freq = max(freq, *target_freq);
++ }
++
++ return 0;
++}
++
++static int get_target_freq_with_devfreq(struct devfreq *devfreq,
+ unsigned long *freq)
+ {
+ struct devfreq_passive_data *p_data
+@@ -99,6 +173,181 @@ no_required_opp:
+ return 0;
+ }
+
++static int devfreq_passive_get_target_freq(struct devfreq *devfreq,
++ unsigned long *freq)
++{
++ struct devfreq_passive_data *p_data =
++ (struct devfreq_passive_data *)devfreq->data;
++ int ret;
++
++ if (!p_data)
++ return -EINVAL;
++
++ /*
++ * If the devfreq device with passive governor has the specific method
++ * to determine the next frequency, should use the get_target_freq()
++ * of struct devfreq_passive_data.
++ */
++ if (p_data->get_target_freq)
++ return p_data->get_target_freq(devfreq, freq);
++
++ switch (p_data->parent_type) {
++ case DEVFREQ_PARENT_DEV:
++ ret = get_target_freq_with_devfreq(devfreq, freq);
++ break;
++ case CPUFREQ_PARENT_DEV:
++ ret = get_target_freq_with_cpufreq(devfreq, freq);
++ break;
++ default:
++ ret = -EINVAL;
++ dev_err(&devfreq->dev, "Invalid parent type\n");
++ break;
++ }
++
++ return ret;
++}
++
++static int cpufreq_passive_notifier_call(struct notifier_block *nb,
++ unsigned long event, void *ptr)
++{
++ struct devfreq_passive_data *p_data =
++ container_of(nb, struct devfreq_passive_data, nb);
++ struct devfreq *devfreq = (struct devfreq *)p_data->this;
++ struct devfreq_cpu_data *parent_cpu_data;
++ struct cpufreq_freqs *freqs = ptr;
++ unsigned int cur_freq;
++ int ret;
++
++ if (event != CPUFREQ_POSTCHANGE || !freqs ||
++ !p_data->parent_cpu_data[freqs->policy->cpu])
++ return 0;
++
++ parent_cpu_data = p_data->parent_cpu_data[freqs->policy->cpu];
++ if (parent_cpu_data->cur_freq == freqs->new)
++ return 0;
++
++ cur_freq = parent_cpu_data->cur_freq;
++ parent_cpu_data->cur_freq = freqs->new;
++
++ mutex_lock(&devfreq->lock);
++ ret = devfreq_update_target(devfreq, freqs->new);
++ mutex_unlock(&devfreq->lock);
++ if (ret) {
++ parent_cpu_data->cur_freq = cur_freq;
++ dev_err(&devfreq->dev, "failed to update the frequency.\n");
++ return ret;
++ }
++
++ return 0;
++}
++
++static int cpufreq_passive_unregister_notifier(struct devfreq *devfreq)
++{
++ struct devfreq_passive_data *p_data
++ = (struct devfreq_passive_data *)devfreq->data;
++ struct devfreq_cpu_data *parent_cpu_data;
++ int cpu, ret;
++
++ if (p_data->nb.notifier_call) {
++ ret = cpufreq_unregister_notifier(&p_data->nb,
++ CPUFREQ_TRANSITION_NOTIFIER);
++ if (ret < 0)
++ return ret;
++ }
++
++ for_each_possible_cpu(cpu) {
++ parent_cpu_data = p_data->parent_cpu_data[cpu];
++ if (!parent_cpu_data)
++ continue;
++
++ if (parent_cpu_data->opp_table)
++ dev_pm_opp_put_opp_table(parent_cpu_data->opp_table);
++ kfree(parent_cpu_data);
++ }
++
++ return 0;
++}
++
++static int cpufreq_passive_register_notifier(struct devfreq *devfreq)
++{
++ struct devfreq_passive_data *p_data
++ = (struct devfreq_passive_data *)devfreq->data;
++ struct device *dev = devfreq->dev.parent;
++ struct opp_table *opp_table = NULL;
++ struct devfreq_cpu_data *parent_cpu_data;
++ struct cpufreq_policy *policy;
++ struct device *cpu_dev;
++ unsigned int cpu;
++ int ret;
++
++ p_data->nb.notifier_call = cpufreq_passive_notifier_call;
++ ret = cpufreq_register_notifier(&p_data->nb, CPUFREQ_TRANSITION_NOTIFIER);
++ if (ret) {
++ dev_err(dev, "failed to register cpufreq notifier\n");
++ p_data->nb.notifier_call = NULL;
++ goto err;
++ }
++
++ for_each_possible_cpu(cpu) {
++ if (p_data->parent_cpu_data[cpu])
++ continue;
++
++ policy = cpufreq_cpu_get(cpu);
++ if (!policy) {
++ ret = -EPROBE_DEFER;
++ goto err;
++ }
++
++ parent_cpu_data = kzalloc(sizeof(*parent_cpu_data),
++ GFP_KERNEL);
++ if (!parent_cpu_data) {
++ ret = -ENOMEM;
++ goto err_put_policy;
++ }
++
++ cpu_dev = get_cpu_device(cpu);
++ if (!cpu_dev) {
++ dev_err(dev, "failed to get cpu device\n");
++ ret = -ENODEV;
++ goto err_free_cpu_data;
++ }
++
++ opp_table = dev_pm_opp_get_opp_table(cpu_dev);
++ if (IS_ERR(opp_table)) {
++ dev_err(dev, "failed to get opp_table of cpu%d\n", cpu);
++ ret = PTR_ERR(opp_table);
++ goto err_free_cpu_data;
++ }
++
++ parent_cpu_data->dev = cpu_dev;
++ parent_cpu_data->opp_table = opp_table;
++ parent_cpu_data->first_cpu = cpumask_first(policy->related_cpus);
++ parent_cpu_data->cur_freq = policy->cur;
++ parent_cpu_data->min_freq = policy->cpuinfo.min_freq;
++ parent_cpu_data->max_freq = policy->cpuinfo.max_freq;
++
++ p_data->parent_cpu_data[cpu] = parent_cpu_data;
++ cpufreq_cpu_put(policy);
++ }
++
++ mutex_lock(&devfreq->lock);
++ ret = devfreq_update_target(devfreq, 0L);
++ mutex_unlock(&devfreq->lock);
++ if (ret)
++ dev_err(dev, "failed to update the frequency\n");
++
++ return ret;
++
++err_free_cpu_data:
++ kfree(parent_cpu_data);
++err_put_policy:
++ cpufreq_cpu_put(policy);
++err:
++ WARN_ON(cpufreq_passive_unregister_notifier(devfreq));
++
++ return ret;
++}
++
+ static int devfreq_passive_notifier_call(struct notifier_block *nb,
+ unsigned long event, void *ptr)
+ {
+@@ -131,30 +380,55 @@ static int devfreq_passive_notifier_call
+ return NOTIFY_DONE;
+ }
+
+-static int devfreq_passive_event_handler(struct devfreq *devfreq,
+- unsigned int event, void *data)
++static int devfreq_passive_unregister_notifier(struct devfreq *devfreq)
++{
++ struct devfreq_passive_data *p_data
++ = (struct devfreq_passive_data *)devfreq->data;
++ struct devfreq *parent = (struct devfreq *)p_data->parent;
++ struct notifier_block *nb = &p_data->nb;
++
++ return devfreq_unregister_notifier(parent, nb, DEVFREQ_TRANSITION_NOTIFIER);
++}
++
++static int devfreq_passive_register_notifier(struct devfreq *devfreq)
+ {
+ struct devfreq_passive_data *p_data
+ = (struct devfreq_passive_data *)devfreq->data;
+ struct devfreq *parent = (struct devfreq *)p_data->parent;
+ struct notifier_block *nb = &p_data->nb;
+- int ret = 0;
+
+ if (!parent)
+ return -EPROBE_DEFER;
+
++ nb->notifier_call = devfreq_passive_notifier_call;
++ return devfreq_register_notifier(parent, nb, DEVFREQ_TRANSITION_NOTIFIER);
++}
++
++static int devfreq_passive_event_handler(struct devfreq *devfreq,
++ unsigned int event, void *data)
++{
++ struct devfreq_passive_data *p_data
++ = (struct devfreq_passive_data *)devfreq->data;
++ int ret = -EINVAL;
++
++ if (!p_data)
++ return -EINVAL;
++
++ if (!p_data->this)
++ p_data->this = devfreq;
++
+ switch (event) {
+ case DEVFREQ_GOV_START:
+- if (!p_data->this)
+- p_data->this = devfreq;
+-
+- nb->notifier_call = devfreq_passive_notifier_call;
+- ret = devfreq_register_notifier(parent, nb,
+- DEVFREQ_TRANSITION_NOTIFIER);
++ if (p_data->parent_type == DEVFREQ_PARENT_DEV)
++ ret = devfreq_passive_register_notifier(devfreq);
++ else if (p_data->parent_type == CPUFREQ_PARENT_DEV)
++ ret = cpufreq_passive_register_notifier(devfreq);
+ break;
+ case DEVFREQ_GOV_STOP:
+- WARN_ON(devfreq_unregister_notifier(parent, nb,
+- DEVFREQ_TRANSITION_NOTIFIER));
++ if (p_data->parent_type == DEVFREQ_PARENT_DEV)
++ WARN_ON(devfreq_passive_unregister_notifier(devfreq));
++ else if (p_data->parent_type == CPUFREQ_PARENT_DEV)
++ WARN_ON(cpufreq_passive_unregister_notifier(devfreq));
+ break;
+ default:
+ break;
+--- a/include/linux/devfreq.h
++++ b/include/linux/devfreq.h
+@@ -38,6 +38,7 @@ enum devfreq_timer {
+
+ struct devfreq;
+ struct devfreq_governor;
++struct devfreq_cpu_data;
+ struct thermal_cooling_device;
+
+ /**
+@@ -288,6 +289,11 @@ struct devfreq_simple_ondemand_data {
+ #endif
+
+ #if IS_ENABLED(CONFIG_DEVFREQ_GOV_PASSIVE)
++enum devfreq_parent_dev_type {
++ DEVFREQ_PARENT_DEV,
++ CPUFREQ_PARENT_DEV,
++};
++
+ /**
+ * struct devfreq_passive_data - ``void *data`` fed to struct devfreq
+ * and devfreq_add_device
+@@ -299,8 +305,11 @@ struct devfreq_simple_ondemand_data {
+ * using governors except for passive governor.
+ * If the devfreq device has the specific method to decide
+ * the next frequency, should use this callback.
+- * @this: the devfreq instance of own device.
+- * @nb: the notifier block for DEVFREQ_TRANSITION_NOTIFIER list
++ * @parent_type: the parent type of the device.
++ * @this: the devfreq instance of own device.
++ * @nb: the notifier block for DEVFREQ_TRANSITION_NOTIFIER or
++ * CPUFREQ_TRANSITION_NOTIFIER list.
++ * @parent_cpu_data: the state min/max/current frequency of all online cpu's.
+ *
+ * The devfreq_passive_data have to set the devfreq instance of parent
+ * device with governors except for the passive governor. But, don't need to
+@@ -314,9 +323,13 @@ struct devfreq_passive_data {
+ /* Optional callback to decide the next frequency of passvice device */
+ int (*get_target_freq)(struct devfreq *this, unsigned long *freq);
+
++ /* Should set the type of parent device */
++ enum devfreq_parent_dev_type parent_type;
++
+ /* For passive governor's internal use. Don't need to set them */
+ struct devfreq *this;
+ struct notifier_block nb;
++ struct devfreq_cpu_data *parent_cpu_data[NR_CPUS];
+ };
+ #endif
+
diff --git a/target/linux/ipq806x/patches-5.15/111-v5.19-03-PM-devfreq-passive-Reduce-duplicate-code-when-passiv.patch b/target/linux/ipq806x/patches-5.15/111-v5.19-03-PM-devfreq-passive-Reduce-duplicate-code-when-passiv.patch
new file mode 100644
index 0000000000..cb10bd1b62
--- /dev/null
+++ b/target/linux/ipq806x/patches-5.15/111-v5.19-03-PM-devfreq-passive-Reduce-duplicate-code-when-passiv.patch
@@ -0,0 +1,110 @@
+From 05723e71234b60a1a47313ea1a889797ec648f1c Mon Sep 17 00:00:00 2001
+From: Chanwoo Choi <cw00.choi@samsung.com>
+Date: Tue, 2 Mar 2021 17:22:50 +0900
+Subject: [PATCH 3/5] PM / devfreq: passive: Reduce duplicate code when
+ passive_devfreq case
+
+In order to keep the consistent coding style between passive_devfreq
+and passive_cpufreq, use common code for handling required opp property.
+Also remove the unneed conditional statement and unify the comment
+of both passive_devfreq and passive_cpufreq when getting the target frequency.
+
+Tested-by: Chen-Yu Tsai <wenst@chromium.org>
+Tested-by: Johnson Wang <johnson.wang@mediatek.com>
+Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
+---
+ drivers/devfreq/governor_passive.c | 66 ++++--------------------------
+ 1 file changed, 8 insertions(+), 58 deletions(-)
+
+--- a/drivers/devfreq/governor_passive.c
++++ b/drivers/devfreq/governor_passive.c
+@@ -93,65 +93,16 @@ static int get_target_freq_with_devfreq(
+ = (struct devfreq_passive_data *)devfreq->data;
+ struct devfreq *parent_devfreq = (struct devfreq *)p_data->parent;
+ unsigned long child_freq = ULONG_MAX;
+- struct dev_pm_opp *opp, *p_opp;
+ int i, count;
+
+- /*
+- * If the devfreq device with passive governor has the specific method
+- * to determine the next frequency, should use the get_target_freq()
+- * of struct devfreq_passive_data.
+- */
+- if (p_data->get_target_freq)
+- return p_data->get_target_freq(devfreq, freq);
+-
+- /*
+- * If the parent and passive devfreq device uses the OPP table,
+- * get the next frequency by using the OPP table.
+- */
+-
+- /*
+- * - parent devfreq device uses the governors except for passive.
+- * - passive devfreq device uses the passive governor.
+- *
+- * Each devfreq has the OPP table. After deciding the new frequency
+- * from the governor of parent devfreq device, the passive governor
+- * need to get the index of new frequency on OPP table of parent
+- * device. And then the index is used for getting the suitable
+- * new frequency for passive devfreq device.
+- */
+- if (!devfreq->profile || !devfreq->profile->freq_table
+- || devfreq->profile->max_state <= 0)
+- return -EINVAL;
+-
+- /*
+- * The passive governor have to get the correct frequency from OPP
+- * list of parent device. Because in this case, *freq is temporary
+- * value which is decided by ondemand governor.
+- */
+- if (devfreq->opp_table && parent_devfreq->opp_table) {
+- p_opp = devfreq_recommended_opp(parent_devfreq->dev.parent,
+- freq, 0);
+- if (IS_ERR(p_opp))
+- return PTR_ERR(p_opp);
+-
+- opp = dev_pm_opp_xlate_required_opp(parent_devfreq->opp_table,
+- devfreq->opp_table, p_opp);
+- dev_pm_opp_put(p_opp);
+-
+- if (IS_ERR(opp))
+- goto no_required_opp;
+-
+- *freq = dev_pm_opp_get_freq(opp);
+- dev_pm_opp_put(opp);
+-
+- return 0;
+- }
++ /* Get target freq via required opps */
++ child_freq = get_target_freq_by_required_opp(parent_devfreq->dev.parent,
++ parent_devfreq->opp_table,
++ devfreq->opp_table, freq);
++ if (child_freq)
++ goto out;
+
+-no_required_opp:
+- /*
+- * Get the OPP table's index of decided frequency by governor
+- * of parent device.
+- */
++ /* Use interpolation if required opps is not available */
+ for (i = 0; i < parent_devfreq->profile->max_state; i++)
+ if (parent_devfreq->profile->freq_table[i] == *freq)
+ break;
+@@ -159,7 +110,6 @@ no_required_opp:
+ if (i == parent_devfreq->profile->max_state)
+ return -EINVAL;
+
+- /* Get the suitable frequency by using index of parent device. */
+ if (i < devfreq->profile->max_state) {
+ child_freq = devfreq->profile->freq_table[i];
+ } else {
+@@ -167,7 +117,7 @@ no_required_opp:
+ child_freq = devfreq->profile->freq_table[count - 1];
+ }
+
+- /* Return the suitable frequency for passive device. */
++out:
+ *freq = child_freq;
+
+ return 0;
diff --git a/target/linux/ipq806x/patches-5.15/111-v5.19-04-PM-devfreq-passive-Keep-cpufreq_policy-for-possible-.patch b/target/linux/ipq806x/patches-5.15/111-v5.19-04-PM-devfreq-passive-Keep-cpufreq_policy-for-possible-.patch
new file mode 100644
index 0000000000..12de8af22e
--- /dev/null
+++ b/target/linux/ipq806x/patches-5.15/111-v5.19-04-PM-devfreq-passive-Keep-cpufreq_policy-for-possible-.patch
@@ -0,0 +1,232 @@
+From 26984d9d581e5049bd75091d2e789b9cc3ea12e0 Mon Sep 17 00:00:00 2001
+From: Chanwoo Choi <cw00.choi@samsung.com>
+Date: Wed, 27 Apr 2022 03:49:19 +0900
+Subject: [PATCH 4/5] PM / devfreq: passive: Keep cpufreq_policy for possible
+ cpus
+
+The passive governor requires the cpu data to get the next target frequency
+of devfreq device if depending on cpu. In order to reduce the unnecessary
+memory data, keep cpufreq_policy data for possible cpus instead of NR_CPU.
+
+Tested-by: Chen-Yu Tsai <wenst@chromium.org>
+Tested-by: Johnson Wang <johnson.wang@mediatek.com>
+Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
+---
+ drivers/devfreq/governor.h | 3 ++
+ drivers/devfreq/governor_passive.c | 75 +++++++++++++++++++++++-------
+ include/linux/devfreq.h | 4 +-
+ 3 files changed, 64 insertions(+), 18 deletions(-)
+
+--- a/drivers/devfreq/governor.h
++++ b/drivers/devfreq/governor.h
+@@ -49,6 +49,7 @@
+
+ /**
+ * struct devfreq_cpu_data - Hold the per-cpu data
++ * @node: list node
+ * @dev: reference to cpu device.
+ * @first_cpu: the cpumask of the first cpu of a policy.
+ * @opp_table: reference to cpu opp table.
+@@ -60,6 +61,8 @@
+ * This is auto-populated by the governor.
+ */
+ struct devfreq_cpu_data {
++ struct list_head node;
++
+ struct device *dev;
+ unsigned int first_cpu;
+
+--- a/drivers/devfreq/governor_passive.c
++++ b/drivers/devfreq/governor_passive.c
+@@ -1,4 +1,4 @@
+-// SPDX-License-Identifier: GPL-2.0-only
++ // SPDX-License-Identifier: GPL-2.0-only
+ /*
+ * linux/drivers/devfreq/governor_passive.c
+ *
+@@ -18,6 +18,22 @@
+
+ #define HZ_PER_KHZ 1000
+
++static struct devfreq_cpu_data *
++get_parent_cpu_data(struct devfreq_passive_data *p_data,
++ struct cpufreq_policy *policy)
++{
++ struct devfreq_cpu_data *parent_cpu_data;
++
++ if (!p_data || !policy)
++ return NULL;
++
++ list_for_each_entry(parent_cpu_data, &p_data->cpu_data_list, node)
++ if (parent_cpu_data->first_cpu == cpumask_first(policy->related_cpus))
++ return parent_cpu_data;
++
++ return NULL;
++}
++
+ static unsigned long get_target_freq_by_required_opp(struct device *p_dev,
+ struct opp_table *p_opp_table,
+ struct opp_table *opp_table,
+@@ -51,14 +67,24 @@ static int get_target_freq_with_cpufreq(
+ struct devfreq_passive_data *p_data =
+ (struct devfreq_passive_data *)devfreq->data;
+ struct devfreq_cpu_data *parent_cpu_data;
++ struct cpufreq_policy *policy;
+ unsigned long cpu, cpu_cur, cpu_min, cpu_max, cpu_percent;
+ unsigned long dev_min, dev_max;
+ unsigned long freq = 0;
++ int ret = 0;
+
+ for_each_online_cpu(cpu) {
+- parent_cpu_data = p_data->parent_cpu_data[cpu];
+- if (!parent_cpu_data || parent_cpu_data->first_cpu != cpu)
++ policy = cpufreq_cpu_get(cpu);
++ if (!policy) {
++ ret = -EINVAL;
++ continue;
++ }
++
++ parent_cpu_data = get_parent_cpu_data(p_data, policy);
++ if (!parent_cpu_data) {
++ cpufreq_cpu_put(policy);
+ continue;
++ }
+
+ /* Get target freq via required opps */
+ cpu_cur = parent_cpu_data->cur_freq * HZ_PER_KHZ;
+@@ -67,6 +93,7 @@ static int get_target_freq_with_cpufreq(
+ devfreq->opp_table, &cpu_cur);
+ if (freq) {
+ *target_freq = max(freq, *target_freq);
++ cpufreq_cpu_put(policy);
+ continue;
+ }
+
+@@ -81,9 +108,10 @@ static int get_target_freq_with_cpufreq(
+ freq = dev_min + mult_frac(dev_max - dev_min, cpu_percent, 100);
+
+ *target_freq = max(freq, *target_freq);
++ cpufreq_cpu_put(policy);
+ }
+
+- return 0;
++ return ret;
+ }
+
+ static int get_target_freq_with_devfreq(struct devfreq *devfreq,
+@@ -168,12 +196,11 @@ static int cpufreq_passive_notifier_call
+ unsigned int cur_freq;
+ int ret;
+
+- if (event != CPUFREQ_POSTCHANGE || !freqs ||
+- !p_data->parent_cpu_data[freqs->policy->cpu])
++ if (event != CPUFREQ_POSTCHANGE || !freqs)
+ return 0;
+
+- parent_cpu_data = p_data->parent_cpu_data[freqs->policy->cpu];
+- if (parent_cpu_data->cur_freq == freqs->new)
++ parent_cpu_data = get_parent_cpu_data(p_data, freqs->policy);
++ if (!parent_cpu_data || parent_cpu_data->cur_freq == freqs->new)
+ return 0;
+
+ cur_freq = parent_cpu_data->cur_freq;
+@@ -196,7 +223,7 @@ static int cpufreq_passive_unregister_no
+ struct devfreq_passive_data *p_data
+ = (struct devfreq_passive_data *)devfreq->data;
+ struct devfreq_cpu_data *parent_cpu_data;
+- int cpu, ret;
++ int cpu, ret = 0;
+
+ if (p_data->nb.notifier_call) {
+ ret = cpufreq_unregister_notifier(&p_data->nb,
+@@ -206,16 +233,26 @@ static int cpufreq_passive_unregister_no
+ }
+
+ for_each_possible_cpu(cpu) {
+- parent_cpu_data = p_data->parent_cpu_data[cpu];
+- if (!parent_cpu_data)
++ struct cpufreq_policy *policy = cpufreq_cpu_get(cpu);
++ if (!policy) {
++ ret = -EINVAL;
++ continue;
++ }
++
++ parent_cpu_data = get_parent_cpu_data(p_data, policy);
++ if (!parent_cpu_data) {
++ cpufreq_cpu_put(policy);
+ continue;
++ }
+
++ list_del(&parent_cpu_data->node);
+ if (parent_cpu_data->opp_table)
+ dev_pm_opp_put_opp_table(parent_cpu_data->opp_table);
+ kfree(parent_cpu_data);
++ cpufreq_cpu_put(policy);
+ }
+
+- return 0;
++ return ret;
+ }
+
+ static int cpufreq_passive_register_notifier(struct devfreq *devfreq)
+@@ -230,6 +267,9 @@ static int cpufreq_passive_register_noti
+ unsigned int cpu;
+ int ret;
+
++ p_data->cpu_data_list
++ = (struct list_head)LIST_HEAD_INIT(p_data->cpu_data_list);
++
+ p_data->nb.notifier_call = cpufreq_passive_notifier_call;
+ ret = cpufreq_register_notifier(&p_data->nb, CPUFREQ_TRANSITION_NOTIFIER);
+ if (ret) {
+@@ -239,15 +279,18 @@ static int cpufreq_passive_register_noti
+ }
+
+ for_each_possible_cpu(cpu) {
+- if (p_data->parent_cpu_data[cpu])
+- continue;
+-
+ policy = cpufreq_cpu_get(cpu);
+ if (!policy) {
+ ret = -EPROBE_DEFER;
+ goto err;
+ }
+
++ parent_cpu_data = get_parent_cpu_data(p_data, policy);
++ if (parent_cpu_data) {
++ cpufreq_cpu_put(policy);
++ continue;
++ }
++
+ parent_cpu_data = kzalloc(sizeof(*parent_cpu_data),
+ GFP_KERNEL);
+ if (!parent_cpu_data) {
+@@ -276,7 +319,7 @@ static int cpufreq_passive_register_noti
+ parent_cpu_data->min_freq = policy->cpuinfo.min_freq;
+ parent_cpu_data->max_freq = policy->cpuinfo.max_freq;
+
+- p_data->parent_cpu_data[cpu] = parent_cpu_data;
++ list_add_tail(&parent_cpu_data->node, &p_data->cpu_data_list);
+ cpufreq_cpu_put(policy);
+ }
+
+--- a/include/linux/devfreq.h
++++ b/include/linux/devfreq.h
+@@ -309,7 +309,7 @@ enum devfreq_parent_dev_type {
+ * @this: the devfreq instance of own device.
+ * @nb: the notifier block for DEVFREQ_TRANSITION_NOTIFIER or
+ * CPUFREQ_TRANSITION_NOTIFIER list.
+- * @parent_cpu_data: the state min/max/current frequency of all online cpu's.
++ * @cpu_data_list: the list of cpu frequency data for all cpufreq_policy.
+ *
+ * The devfreq_passive_data have to set the devfreq instance of parent
+ * device with governors except for the passive governor. But, don't need to
+@@ -329,7 +329,7 @@ struct devfreq_passive_data {
+ /* For passive governor's internal use. Don't need to set them */
+ struct devfreq *this;
+ struct notifier_block nb;
+- struct devfreq_cpu_data *parent_cpu_data[NR_CPUS];
++ struct list_head cpu_data_list;
+ };
+ #endif
+
diff --git a/target/linux/ipq806x/patches-5.15/111-v5.19-05-PM-devfreq-passive-Return-non-error-when-not-support.patch b/target/linux/ipq806x/patches-5.15/111-v5.19-05-PM-devfreq-passive-Return-non-error-when-not-support.patch
new file mode 100644
index 0000000000..69160fd77a
--- /dev/null
+++ b/target/linux/ipq806x/patches-5.15/111-v5.19-05-PM-devfreq-passive-Return-non-error-when-not-support.patch
@@ -0,0 +1,31 @@
+From 42d2607d91c4ec37ea1970899c2d614824f3014b Mon Sep 17 00:00:00 2001
+From: Chanwoo Choi <cw00.choi@samsung.com>
+Date: Thu, 19 May 2022 10:07:53 +0900
+Subject: [PATCH 5/5] PM / devfreq: passive: Return non-error when
+ not-supported event is required
+
+Each devfreq governor specifies the supported governor event
+such as GOV_START and GOV_STOP. When not-supported event is required,
+just return non-error. But, commit ce9a0d88d97a ("PM / devfreq: Add
+cpu based scaling support to passive governor") returned the error
+value. So that return non-error value when not-supported event is required.
+
+Fixes: ce9a0d88d97a ("PM / devfreq: Add cpu based scaling support to passive governor")
+Reported-by: Marek Szyprowski <m.szyprowski@samsung.com>
+Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+---
+ drivers/devfreq/governor_passive.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/devfreq/governor_passive.c
++++ b/drivers/devfreq/governor_passive.c
+@@ -402,7 +402,7 @@ static int devfreq_passive_event_handler
+ {
+ struct devfreq_passive_data *p_data
+ = (struct devfreq_passive_data *)devfreq->data;
+- int ret = -EINVAL;
++ int ret = 0;
+
+ if (!p_data)
+ return -EINVAL;
diff --git a/target/linux/ipq806x/patches-5.15/112-v5.19-PM-devfreq-Fix-kernel-warning-with-cpufreq-passive-r.patch b/target/linux/ipq806x/patches-5.15/112-v5.19-PM-devfreq-Fix-kernel-warning-with-cpufreq-passive-r.patch
new file mode 100644
index 0000000000..3d2bb2de05
--- /dev/null
+++ b/target/linux/ipq806x/patches-5.15/112-v5.19-PM-devfreq-Fix-kernel-warning-with-cpufreq-passive-r.patch
@@ -0,0 +1,31 @@
+From 82c66d2bbbeda9e493487e7413769087a0b46250 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 20 Jun 2022 00:29:39 +0200
+Subject: [PATCH 1/1] PM / devfreq: Fix kernel warning with cpufreq passive
+ register fail
+
+Remove cpufreq_passive_unregister_notifier from
+cpufreq_passive_register_notifier in case of error as devfreq core
+already call unregister on GOV_START fail.
+
+This fix the kernel always printing a WARN on governor PROBE_DEFER as
+cpufreq_passive_unregister_notifier is called two times and return
+error on the second call as the cpufreq is already unregistered.
+
+Fixes: a03dacb0316f ("PM / devfreq: Add cpu based scaling support to passive governor")
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
+---
+ drivers/devfreq/governor_passive.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+--- a/drivers/devfreq/governor_passive.c
++++ b/drivers/devfreq/governor_passive.c
+@@ -336,7 +336,6 @@ err_free_cpu_data:
+ err_put_policy:
+ cpufreq_cpu_put(policy);
+ err:
+- WARN_ON(cpufreq_passive_unregister_notifier(devfreq));
+
+ return ret;
+ }
diff --git a/target/linux/ipq806x/patches-5.15/113-v5.19-01-PM-devfreq-Fix-cpufreq-passive-unregister-errorin.patch b/target/linux/ipq806x/patches-5.15/113-v5.19-01-PM-devfreq-Fix-cpufreq-passive-unregister-errorin.patch
new file mode 100644
index 0000000000..0883424548
--- /dev/null
+++ b/target/linux/ipq806x/patches-5.15/113-v5.19-01-PM-devfreq-Fix-cpufreq-passive-unregister-errorin.patch
@@ -0,0 +1,85 @@
+From 8953603eb5447be52f6fc3d8fcae1b3ce9899189 Mon Sep 17 00:00:00 2001
+From: Christian 'Ansuel' Marangi <ansuelsmth@gmail.com>
+Date: Mon, 6 Jun 2022 11:58:49 +0200
+Subject: [PATCH v4 1/4] PM / devfreq: Fix cpufreq passive unregister erroring
+ on PROBE_DEFER
+
+With the passive governor, the cpu based scaling can PROBE_DEFER due to
+the fact that CPU policy are not ready.
+The cpufreq passive unregister notifier is called both from the
+GOV_START errors and for the GOV_STOP and assume the notifier is
+successfully registred every time. With GOV_START failing it's wrong to
+loop over each possible CPU since the register path has failed for
+some CPU policy not ready. Change the logic and unregister the notifer
+based on the current allocated parent_cpu_data list to correctly handle
+errors and the governor unregister path.
+
+Fixes: a03dacb0316f ("PM / devfreq: Add cpu based scaling support to passive governor")
+Signed-off-by: Christian 'Ansuel' Marangi <ansuelsmth@gmail.com>
+---
+ drivers/devfreq/governor_passive.c | 39 +++++++++++++-----------------
+ 1 file changed, 17 insertions(+), 22 deletions(-)
+
+--- a/drivers/devfreq/governor_passive.c
++++ b/drivers/devfreq/governor_passive.c
+@@ -34,6 +34,20 @@ get_parent_cpu_data(struct devfreq_passi
+ return NULL;
+ }
+
++static void delete_parent_cpu_data(struct devfreq_passive_data *p_data)
++{
++ struct devfreq_cpu_data *parent_cpu_data, *tmp;
++
++ list_for_each_entry_safe(parent_cpu_data, tmp, &p_data->cpu_data_list, node) {
++ list_del(&parent_cpu_data->node);
++
++ if (parent_cpu_data->opp_table)
++ dev_pm_opp_put_opp_table(parent_cpu_data->opp_table);
++
++ kfree(parent_cpu_data);
++ }
++}
++
+ static unsigned long get_target_freq_by_required_opp(struct device *p_dev,
+ struct opp_table *p_opp_table,
+ struct opp_table *opp_table,
+@@ -222,8 +236,7 @@ static int cpufreq_passive_unregister_no
+ {
+ struct devfreq_passive_data *p_data
+ = (struct devfreq_passive_data *)devfreq->data;
+- struct devfreq_cpu_data *parent_cpu_data;
+- int cpu, ret = 0;
++ int ret;
+
+ if (p_data->nb.notifier_call) {
+ ret = cpufreq_unregister_notifier(&p_data->nb,
+@@ -232,27 +245,9 @@ static int cpufreq_passive_unregister_no
+ return ret;
+ }
+
+- for_each_possible_cpu(cpu) {
+- struct cpufreq_policy *policy = cpufreq_cpu_get(cpu);
+- if (!policy) {
+- ret = -EINVAL;
+- continue;
+- }
+-
+- parent_cpu_data = get_parent_cpu_data(p_data, policy);
+- if (!parent_cpu_data) {
+- cpufreq_cpu_put(policy);
+- continue;
+- }
+-
+- list_del(&parent_cpu_data->node);
+- if (parent_cpu_data->opp_table)
+- dev_pm_opp_put_opp_table(parent_cpu_data->opp_table);
+- kfree(parent_cpu_data);
+- cpufreq_cpu_put(policy);
+- }
++ delete_parent_cpu_data(p_data);
+
+- return ret;
++ return 0;
+ }
+
+ static int cpufreq_passive_register_notifier(struct devfreq *devfreq)
diff --git a/target/linux/ipq806x/patches-5.15/113-v5.19-02-PM-devfreq-Fix-kernel-panic-with-cpu-based-scaling-t.patch b/target/linux/ipq806x/patches-5.15/113-v5.19-02-PM-devfreq-Fix-kernel-panic-with-cpu-based-scaling-t.patch
new file mode 100644
index 0000000000..84cb67bc99
--- /dev/null
+++ b/target/linux/ipq806x/patches-5.15/113-v5.19-02-PM-devfreq-Fix-kernel-panic-with-cpu-based-scaling-t.patch
@@ -0,0 +1,39 @@
+From 57e00b40033a376de3f3cf0bb9bf7590d2dd679d Mon Sep 17 00:00:00 2001
+From: Christian 'Ansuel' Marangi <ansuelsmth@gmail.com>
+Date: Tue, 14 Jun 2022 13:06:59 +0200
+Subject: [PATCH 1/1] PM / devfreq: Fix kernel panic with cpu based scaling to
+ passive gov
+
+The cpufreq passive register notifier can PROBE_DEFER and the devfreq
+struct is freed and then reallocaed on probe retry.
+The current logic assume that the code can't PROBE_DEFER so the devfreq
+struct in the this variable in devfreq_passive_data is assumed to be
+(if already set) always correct.
+This cause kernel panic as the code try to access the wrong address.
+To correctly handle this, update the this variable in
+devfreq_passive_data to the devfreq reallocated struct.
+
+Fixes: a03dacb0316f ("PM / devfreq: Add cpu based scaling support to passive governor")
+Signed-off-by: Christian 'Ansuel' Marangi <ansuelsmth@gmail.com>
+Signed-off-by: Chanwoo Choi <cw00.choi@samsung.com>
+---
+ drivers/devfreq/governor_passive.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/drivers/devfreq/governor_passive.c b/drivers/devfreq/governor_passive.c
+index 72c67979ebe1..091a69e1f487 100644
+--- a/drivers/devfreq/governor_passive.c
++++ b/drivers/devfreq/governor_passive.c
+@@ -407,8 +407,7 @@ static int devfreq_passive_event_handler(struct devfreq *devfreq,
+ if (!p_data)
+ return -EINVAL;
+
+- if (!p_data->this)
+- p_data->this = devfreq;
++ p_data->this = devfreq;
+
+ switch (event) {
+ case DEVFREQ_GOV_START:
+--
+2.37.2
+
diff --git a/target/linux/ipq806x/patches-5.15/113-v5.19-03-PM-devfreq-Rework-freq_table-to-be-local-to-devfr.patch b/target/linux/ipq806x/patches-5.15/113-v5.19-03-PM-devfreq-Rework-freq_table-to-be-local-to-devfr.patch
new file mode 100644
index 0000000000..0813140eca
--- /dev/null
+++ b/target/linux/ipq806x/patches-5.15/113-v5.19-03-PM-devfreq-Rework-freq_table-to-be-local-to-devfr.patch
@@ -0,0 +1,269 @@
+From 46d05776a1a5dd8eb479e868f5ff4f4b97d68238 Mon Sep 17 00:00:00 2001
+From: Christian 'Ansuel' Marangi <ansuelsmth@gmail.com>
+Date: Mon, 6 Jun 2022 12:39:19 +0200
+Subject: [PATCH v4 3/4] PM / devfreq: Rework freq_table to be local to devfreq
+ struct
+
+Currently we reference the freq_table to the profile defined one and we
+make changes on it. Devfreq never supported PROBE_DEFER before the cpu
+based scaling support to the passive governor and assumed that a devfreq
+device could only had error and be done with it.
+Now that a device can PROBE_DEFER a rework to the freq_table logic is
+required.
+
+If a device PROBE_DEFER on the GOV_START, the freq_table is already set
+in the device profile struct and its init is skipped. This is due to the
+fact that it's common for devs to declare this kind of struct static.
+This cause the devfreq logic to find a freq table declared (freq_table
+not NULL) with random data and poiting to the old addrs freed by devm.
+
+This problem CAN be solved by devs by clearing the freq_table in their
+profile struct on driver exit path but it should not be trusted and it
+looks to use a flawed logic.
+
+A better solution is to move the freq_table and max_state to the
+devfreq struct and never change the profile struct.
+This permit to correctly handle PROBE_DEFER since the devfreq struct is
+reallocated and contains new values.
+Also the profile struct should only be used to init the driver and should
+not be used by the devfreq to write the freq_table if it's not provided
+by the driver.
+
+Fixes: a03dacb0316f ("PM / devfreq: Add cpu based scaling support to passive governor")
+Signed-off-by: Christian 'Ansuel' Marangi <ansuelsmth@gmail.com>
+---
+ drivers/devfreq/devfreq.c | 71 ++++++++++++++----------------
+ drivers/devfreq/governor_passive.c | 14 +++---
+ include/linux/devfreq.h | 4 ++
+ 3 files changed, 45 insertions(+), 44 deletions(-)
+
+--- a/drivers/devfreq/devfreq.c
++++ b/drivers/devfreq/devfreq.c
+@@ -123,7 +123,7 @@ void devfreq_get_freq_range(struct devfr
+ unsigned long *min_freq,
+ unsigned long *max_freq)
+ {
+- unsigned long *freq_table = devfreq->profile->freq_table;
++ unsigned long *freq_table = devfreq->freq_table;
+ s32 qos_min_freq, qos_max_freq;
+
+ lockdep_assert_held(&devfreq->lock);
+@@ -133,11 +133,11 @@ void devfreq_get_freq_range(struct devfr
+ * The devfreq drivers can initialize this in either ascending or
+ * descending order and devfreq core supports both.
+ */
+- if (freq_table[0] < freq_table[devfreq->profile->max_state - 1]) {
++ if (freq_table[0] < freq_table[devfreq->max_state - 1]) {
+ *min_freq = freq_table[0];
+- *max_freq = freq_table[devfreq->profile->max_state - 1];
++ *max_freq = freq_table[devfreq->max_state - 1];
+ } else {
+- *min_freq = freq_table[devfreq->profile->max_state - 1];
++ *min_freq = freq_table[devfreq->max_state - 1];
+ *max_freq = freq_table[0];
+ }
+
+@@ -169,8 +169,8 @@ static int devfreq_get_freq_level(struct
+ {
+ int lev;
+
+- for (lev = 0; lev < devfreq->profile->max_state; lev++)
+- if (freq == devfreq->profile->freq_table[lev])
++ for (lev = 0; lev < devfreq->max_state; lev++)
++ if (freq == devfreq->freq_table[lev])
+ return lev;
+
+ return -EINVAL;
+@@ -178,7 +178,6 @@ static int devfreq_get_freq_level(struct
+
+ static int set_freq_table(struct devfreq *devfreq)
+ {
+- struct devfreq_dev_profile *profile = devfreq->profile;
+ struct dev_pm_opp *opp;
+ unsigned long freq;
+ int i, count;
+@@ -188,25 +187,22 @@ static int set_freq_table(struct devfreq
+ if (count <= 0)
+ return -EINVAL;
+
+- profile->max_state = count;
+- profile->freq_table = devm_kcalloc(devfreq->dev.parent,
+- profile->max_state,
+- sizeof(*profile->freq_table),
+- GFP_KERNEL);
+- if (!profile->freq_table) {
+- profile->max_state = 0;
++ devfreq->max_state = count;
++ devfreq->freq_table = devm_kcalloc(devfreq->dev.parent,
++ devfreq->max_state,
++ sizeof(*devfreq->freq_table),
++ GFP_KERNEL);
++ if (!devfreq->freq_table)
+ return -ENOMEM;
+- }
+
+- for (i = 0, freq = 0; i < profile->max_state; i++, freq++) {
++ for (i = 0, freq = 0; i < devfreq->max_state; i++, freq++) {
+ opp = dev_pm_opp_find_freq_ceil(devfreq->dev.parent, &freq);
+ if (IS_ERR(opp)) {
+- devm_kfree(devfreq->dev.parent, profile->freq_table);
+- profile->max_state = 0;
++ devm_kfree(devfreq->dev.parent, devfreq->freq_table);
+ return PTR_ERR(opp);
+ }
+ dev_pm_opp_put(opp);
+- profile->freq_table[i] = freq;
++ devfreq->freq_table[i] = freq;
+ }
+
+ return 0;
+@@ -246,7 +242,7 @@ int devfreq_update_status(struct devfreq
+
+ if (lev != prev_lev) {
+ devfreq->stats.trans_table[
+- (prev_lev * devfreq->profile->max_state) + lev]++;
++ (prev_lev * devfreq->max_state) + lev]++;
+ devfreq->stats.total_trans++;
+ }
+
+@@ -835,6 +831,9 @@ struct devfreq *devfreq_add_device(struc
+ if (err < 0)
+ goto err_dev;
+ mutex_lock(&devfreq->lock);
++ } else {
++ devfreq->freq_table = devfreq->profile->freq_table;
++ devfreq->max_state = devfreq->profile->max_state;
+ }
+
+ devfreq->scaling_min_freq = find_available_min_freq(devfreq);
+@@ -870,8 +869,8 @@ struct devfreq *devfreq_add_device(struc
+
+ devfreq->stats.trans_table = devm_kzalloc(&devfreq->dev,
+ array3_size(sizeof(unsigned int),
+- devfreq->profile->max_state,
+- devfreq->profile->max_state),
++ devfreq->max_state,
++ devfreq->max_state),
+ GFP_KERNEL);
+ if (!devfreq->stats.trans_table) {
+ mutex_unlock(&devfreq->lock);
+@@ -880,7 +879,7 @@ struct devfreq *devfreq_add_device(struc
+ }
+
+ devfreq->stats.time_in_state = devm_kcalloc(&devfreq->dev,
+- devfreq->profile->max_state,
++ devfreq->max_state,
+ sizeof(*devfreq->stats.time_in_state),
+ GFP_KERNEL);
+ if (!devfreq->stats.time_in_state) {
+@@ -1639,9 +1638,9 @@ static ssize_t available_frequencies_sho
+
+ mutex_lock(&df->lock);
+
+- for (i = 0; i < df->profile->max_state; i++)
++ for (i = 0; i < df->max_state; i++)
+ count += scnprintf(&buf[count], (PAGE_SIZE - count - 2),
+- "%lu ", df->profile->freq_table[i]);
++ "%lu ", df->freq_table[i]);
+
+ mutex_unlock(&df->lock);
+ /* Truncate the trailing space */
+@@ -1664,7 +1663,7 @@ static ssize_t trans_stat_show(struct de
+
+ if (!df->profile)
+ return -EINVAL;
+- max_state = df->profile->max_state;
++ max_state = df->max_state;
+
+ if (max_state == 0)
+ return sprintf(buf, "Not Supported.\n");
+@@ -1681,19 +1680,17 @@ static ssize_t trans_stat_show(struct de
+ len += sprintf(buf + len, " :");
+ for (i = 0; i < max_state; i++)
+ len += sprintf(buf + len, "%10lu",
+- df->profile->freq_table[i]);
++ df->freq_table[i]);
+
+ len += sprintf(buf + len, " time(ms)\n");
+
+ for (i = 0; i < max_state; i++) {
+- if (df->profile->freq_table[i]
+- == df->previous_freq) {
++ if (df->freq_table[i] == df->previous_freq)
+ len += sprintf(buf + len, "*");
+- } else {
++ else
+ len += sprintf(buf + len, " ");
+- }
+- len += sprintf(buf + len, "%10lu:",
+- df->profile->freq_table[i]);
++
++ len += sprintf(buf + len, "%10lu:", df->freq_table[i]);
+ for (j = 0; j < max_state; j++)
+ len += sprintf(buf + len, "%10u",
+ df->stats.trans_table[(i * max_state) + j]);
+@@ -1717,7 +1714,7 @@ static ssize_t trans_stat_store(struct d
+ if (!df->profile)
+ return -EINVAL;
+
+- if (df->profile->max_state == 0)
++ if (df->max_state == 0)
+ return count;
+
+ err = kstrtoint(buf, 10, &value);
+@@ -1725,11 +1722,11 @@ static ssize_t trans_stat_store(struct d
+ return -EINVAL;
+
+ mutex_lock(&df->lock);
+- memset(df->stats.time_in_state, 0, (df->profile->max_state *
++ memset(df->stats.time_in_state, 0, (df->max_state *
+ sizeof(*df->stats.time_in_state)));
+ memset(df->stats.trans_table, 0, array3_size(sizeof(unsigned int),
+- df->profile->max_state,
+- df->profile->max_state));
++ df->max_state,
++ df->max_state));
+ df->stats.total_trans = 0;
+ df->stats.last_update = get_jiffies_64();
+ mutex_unlock(&df->lock);
+--- a/drivers/devfreq/governor_passive.c
++++ b/drivers/devfreq/governor_passive.c
+@@ -145,18 +145,18 @@ static int get_target_freq_with_devfreq(
+ goto out;
+
+ /* Use interpolation if required opps is not available */
+- for (i = 0; i < parent_devfreq->profile->max_state; i++)
+- if (parent_devfreq->profile->freq_table[i] == *freq)
++ for (i = 0; i < parent_devfreq->max_state; i++)
++ if (parent_devfreq->freq_table[i] == *freq)
+ break;
+
+- if (i == parent_devfreq->profile->max_state)
++ if (i == parent_devfreq->max_state)
+ return -EINVAL;
+
+- if (i < devfreq->profile->max_state) {
+- child_freq = devfreq->profile->freq_table[i];
++ if (i < devfreq->max_state) {
++ child_freq = devfreq->freq_table[i];
+ } else {
+- count = devfreq->profile->max_state;
+- child_freq = devfreq->profile->freq_table[count - 1];
++ count = devfreq->max_state;
++ child_freq = devfreq->freq_table[count - 1];
+ }
+
+ out:
+--- a/include/linux/devfreq.h
++++ b/include/linux/devfreq.h
+@@ -185,6 +185,10 @@ struct devfreq {
+ struct notifier_block nb;
+ struct delayed_work work;
+
++ /* devfreq local freq_table */
++ unsigned long *freq_table;
++ unsigned int max_state;
++
+ unsigned long previous_freq;
+ struct devfreq_dev_status last_status;
+
diff --git a/target/linux/ipq806x/patches-5.15/113-v5.19-04-PM-devfreq-Mute-warning-on-governor-PROBE_DEFER.patch b/target/linux/ipq806x/patches-5.15/113-v5.19-04-PM-devfreq-Mute-warning-on-governor-PROBE_DEFER.patch
new file mode 100644
index 0000000000..cfdef8d6c5
--- /dev/null
+++ b/target/linux/ipq806x/patches-5.15/113-v5.19-04-PM-devfreq-Mute-warning-on-governor-PROBE_DEFER.patch
@@ -0,0 +1,28 @@
+From eee9f767c41b03a2744d4b0f0c1a144e4ff41e78 Mon Sep 17 00:00:00 2001
+From: Christian 'Ansuel' Marangi <ansuelsmth@gmail.com>
+Date: Mon, 6 Jun 2022 13:01:02 +0200
+Subject: [PATCH v4 4/4] PM / devfreq: Mute warning on governor PROBE_DEFER
+
+Don't print warning when a governor PROBE_DEFER as it's not a real
+GOV_START fail.
+
+Fixes: a03dacb0316f ("PM / devfreq: Add cpu based scaling support to passive governor")
+Signed-off-by: Christian 'Ansuel' Marangi <ansuelsmth@gmail.com>
+---
+ drivers/devfreq/devfreq.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+--- a/drivers/devfreq/devfreq.c
++++ b/drivers/devfreq/devfreq.c
+@@ -931,8 +931,9 @@ struct devfreq *devfreq_add_device(struc
+ err = devfreq->governor->event_handler(devfreq, DEVFREQ_GOV_START,
+ NULL);
+ if (err) {
+- dev_err(dev, "%s: Unable to start governor for the device\n",
+- __func__);
++ dev_err_probe(dev, err,
++ "%s: Unable to start governor for the device\n",
++ __func__);
+ goto err_init;
+ }
+ create_sysfs_files(devfreq, devfreq->governor);