Commit 854e3349 authored by Rafael J. Wysocki's avatar Rafael J. Wysocki

Merge tag 'devfreq-next-for-5.6' of git://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/linux

Pull devfreq updates for v5.6 from Chanwoo Choi:

"1. Update devfreq core

 - Add new 'name' attribute of sysfs to show the device name
   : /sys/class/devfreq/devfreqX/name

 - Make 'trans_stat' sysfs reset by entering zero(0)
   : echo 0 > /sys/class/devfreq/devfreqX/trans_stat

 - Add debugfs support with 'devfreq_summary' to show the summary
   : /sys/kernel/debug/devfreq/devfreq_summary

 - Change the type of time variable to 64bit to avoid overflows.

 - Make separate devfreq_stats including the statistics information.

 - Fix minor coding-style like indentation and kernel-doc warnings.

 2. Update devfreq drivers

 - Add new imx8m-ddrc.c devfreq driver for dynamic scaling of DDR frequency.
   It changes the DDR frequency by using ARM SMCCC(SMC Calling Convention)
   interface to control TF-A firmware.

 - Add COMPILE_TEST dependency for rk3399_dmc.c.

 - Clean-up code for exynos-bus.c and rk3399_dmc.c without behavior changes

 3. Update devfreq-event drivers

 - Fix excessive stack usage of exynos-ppmu.c and clean-up code of
   rockchip-dfi.c without behavior changes."

* tag 'devfreq-next-for-5.6' of git://git.kernel.org/pub/scm/linux/kernel/git/chanwoo/linux: (24 commits)
  PM / devfreq: Add debugfs support with devfreq_summary file
  PM / devfreq: exynos: Rename Exynos to lowercase
  PM / devfreq: imx8m-ddrc: Fix inconsistent IS_ERR and PTR_ERR
  PM / devfreq: exynos-bus: Add error log when fail to get devfreq-event
  PM / devfreq: exynos-bus: Disable devfreq-event device when fails
  PM / devfreq: rk3399_dmc: Disable devfreq-event device when fails
  PM / devfreq: imx8m-ddrc: Remove unused defines
  PM / devfreq: exynos-bus: Reduce goto statements and remove unused headers
  PM / devfreq: rk3399_dmc: Add COMPILE_TEST and HAVE_ARM_SMCCC dependency
  PM / devfreq: rockchip-dfi: Convert to devm_platform_ioremap_resource
  PM / devfreq: rk3399_dmc: Add missing of_node_put()
  PM / devfreq: rockchip-dfi: Add missing of_node_put()
  PM / devfreq: Fix multiple kernel-doc warnings
  PM / devfreq: exynos-bus: Extract exynos_bus_profile_init_passive()
  PM / devfreq: exynos-bus: Extract exynos_bus_profile_init()
  PM / devfreq: Move declaration of DEVICE_ATTR_RW(min_freq)
  PM / devfreq: Move statistics to separate struct devfreq_stats
  PM / devfreq: Add clearing transitions stats
  PM / devfreq: Change time stats to 64-bit
  PM / devfreq: Add new name attribute for sysfs
  ...
parents b3a987b0 490a421b
...@@ -7,6 +7,13 @@ Description: ...@@ -7,6 +7,13 @@ Description:
The name of devfreq object denoted as ... is same as the The name of devfreq object denoted as ... is same as the
name of device using devfreq. name of device using devfreq.
What: /sys/class/devfreq/.../name
Date: November 2019
Contact: Chanwoo Choi <cw00.choi@samsung.com>
Description:
The /sys/class/devfreq/.../name shows the name of device
of the corresponding devfreq object.
What: /sys/class/devfreq/.../governor What: /sys/class/devfreq/.../governor
Date: September 2011 Date: September 2011
Contact: MyungJoo Ham <myungjoo.ham@samsung.com> Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
...@@ -48,12 +55,15 @@ What: /sys/class/devfreq/.../trans_stat ...@@ -48,12 +55,15 @@ What: /sys/class/devfreq/.../trans_stat
Date: October 2012 Date: October 2012
Contact: MyungJoo Ham <myungjoo.ham@samsung.com> Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
Description: Description:
This ABI shows the statistics of devfreq behavior on a This ABI shows or clears the statistics of devfreq behavior
specific device. It shows the time spent in each state and on a specific device. It shows the time spent in each state
the number of transitions between states. and the number of transitions between states.
In order to activate this ABI, the devfreq target device In order to activate this ABI, the devfreq target device
driver should provide the list of available frequencies driver should provide the list of available frequencies
with its profile. with its profile. If need to reset the statistics of devfreq
behavior on a specific device, enter 0(zero) to 'trans_stat'
as following:
echo 0 > /sys/class/devfreq/.../trans_stat
What: /sys/class/devfreq/.../userspace/set_freq What: /sys/class/devfreq/.../userspace/set_freq
Date: September 2011 Date: September 2011
......
# SPDX-License-Identifier: GPL-2.0
%YAML 1.2
---
$id: http://devicetree.org/schemas/memory-controllers/fsl/imx8m-ddrc.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: i.MX8M DDR Controller
maintainers:
- Leonard Crestez <leonard.crestez@nxp.com>
description:
The DDRC block is integrated in i.MX8M for interfacing with DDR based
memories.
It supports switching between different frequencies at runtime but during
this process RAM itself becomes briefly inaccessible so actual frequency
switching is implemented by TF-A code which runs from a SRAM area.
The Linux driver for the DDRC doesn't even map registers (they're included
for the sake of "describing hardware"), it mostly just exposes firmware
capabilities through standard Linux mechanism like devfreq and OPP tables.
properties:
compatible:
items:
- enum:
- fsl,imx8mn-ddrc
- fsl,imx8mm-ddrc
- fsl,imx8mq-ddrc
- const: fsl,imx8m-ddrc
reg:
maxItems: 1
description:
Base address and size of DDRC CTL area.
This is not currently mapped by the imx8m-ddrc driver.
clocks:
maxItems: 4
clock-names:
items:
- const: core
- const: pll
- const: alt
- const: apb
operating-points-v2: true
opp-table: true
required:
- reg
- compatible
- clocks
- clock-names
additionalProperties: false
examples:
- |
#include <dt-bindings/clock/imx8mm-clock.h>
ddrc: memory-controller@3d400000 {
compatible = "fsl,imx8mm-ddrc", "fsl,imx8m-ddrc";
reg = <0x3d400000 0x400000>;
clock-names = "core", "pll", "alt", "apb";
clocks = <&clk IMX8MM_CLK_DRAM_CORE>,
<&clk IMX8MM_DRAM_PLL>,
<&clk IMX8MM_CLK_DRAM_ALT>,
<&clk IMX8MM_CLK_DRAM_APB>;
operating-points-v2 = <&ddrc_opp_table>;
};
...@@ -77,7 +77,7 @@ config DEVFREQ_GOV_PASSIVE ...@@ -77,7 +77,7 @@ config DEVFREQ_GOV_PASSIVE
comment "DEVFREQ Drivers" comment "DEVFREQ Drivers"
config ARM_EXYNOS_BUS_DEVFREQ config ARM_EXYNOS_BUS_DEVFREQ
tristate "ARM EXYNOS Generic Memory Bus DEVFREQ Driver" tristate "ARM Exynos Generic Memory Bus DEVFREQ Driver"
depends on ARCH_EXYNOS || COMPILE_TEST depends on ARCH_EXYNOS || COMPILE_TEST
select DEVFREQ_GOV_SIMPLE_ONDEMAND select DEVFREQ_GOV_SIMPLE_ONDEMAND
select DEVFREQ_GOV_PASSIVE select DEVFREQ_GOV_PASSIVE
...@@ -91,6 +91,16 @@ config ARM_EXYNOS_BUS_DEVFREQ ...@@ -91,6 +91,16 @@ config ARM_EXYNOS_BUS_DEVFREQ
and adjusts the operating frequencies and voltages with OPP support. and adjusts the operating frequencies and voltages with OPP support.
This does not yet operate with optimal voltages. This does not yet operate with optimal voltages.
config ARM_IMX8M_DDRC_DEVFREQ
tristate "i.MX8M DDRC DEVFREQ Driver"
depends on (ARCH_MXC && HAVE_ARM_SMCCC) || \
(COMPILE_TEST && HAVE_ARM_SMCCC)
select DEVFREQ_GOV_SIMPLE_ONDEMAND
select DEVFREQ_GOV_USERSPACE
help
This adds the DEVFREQ driver for the i.MX8M DDR Controller. It allows
adjusting DRAM frequency.
config ARM_TEGRA_DEVFREQ config ARM_TEGRA_DEVFREQ
tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver" tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \ depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
...@@ -115,14 +125,15 @@ config ARM_TEGRA20_DEVFREQ ...@@ -115,14 +125,15 @@ config ARM_TEGRA20_DEVFREQ
config ARM_RK3399_DMC_DEVFREQ config ARM_RK3399_DMC_DEVFREQ
tristate "ARM RK3399 DMC DEVFREQ Driver" tristate "ARM RK3399 DMC DEVFREQ Driver"
depends on ARCH_ROCKCHIP depends on (ARCH_ROCKCHIP && HAVE_ARM_SMCCC) || \
(COMPILE_TEST && HAVE_ARM_SMCCC)
select DEVFREQ_EVENT_ROCKCHIP_DFI select DEVFREQ_EVENT_ROCKCHIP_DFI
select DEVFREQ_GOV_SIMPLE_ONDEMAND select DEVFREQ_GOV_SIMPLE_ONDEMAND
select PM_DEVFREQ_EVENT select PM_DEVFREQ_EVENT
help help
This adds the DEVFREQ driver for the RK3399 DMC(Dynamic Memory Controller). This adds the DEVFREQ driver for the RK3399 DMC(Dynamic Memory Controller).
It sets the frequency for the memory controller and reads the usage counts It sets the frequency for the memory controller and reads the usage counts
from hardware. from hardware.
source "drivers/devfreq/event/Kconfig" source "drivers/devfreq/event/Kconfig"
......
...@@ -9,6 +9,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o ...@@ -9,6 +9,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o
# DEVFREQ Drivers # DEVFREQ Drivers
obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o
obj-$(CONFIG_ARM_IMX8M_DDRC_DEVFREQ) += imx8m-ddrc.o
obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ) += rk3399_dmc.o obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ) += rk3399_dmc.o
obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra30-devfreq.o obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra30-devfreq.o
obj-$(CONFIG_ARM_TEGRA20_DEVFREQ) += tegra20-devfreq.o obj-$(CONFIG_ARM_TEGRA20_DEVFREQ) += tegra20-devfreq.o
......
...@@ -346,9 +346,9 @@ EXPORT_SYMBOL_GPL(devfreq_event_add_edev); ...@@ -346,9 +346,9 @@ EXPORT_SYMBOL_GPL(devfreq_event_add_edev);
/** /**
* devfreq_event_remove_edev() - Remove the devfreq-event device registered. * devfreq_event_remove_edev() - Remove the devfreq-event device registered.
* @dev : the devfreq-event device * @edev : the devfreq-event device
* *
* Note that this function remove the registered devfreq-event device. * Note that this function removes the registered devfreq-event device.
*/ */
int devfreq_event_remove_edev(struct devfreq_event_dev *edev) int devfreq_event_remove_edev(struct devfreq_event_dev *edev)
{ {
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/kmod.h> #include <linux/kmod.h>
#include <linux/sched.h> #include <linux/sched.h>
#include <linux/debugfs.h>
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/init.h> #include <linux/init.h>
...@@ -33,6 +34,7 @@ ...@@ -33,6 +34,7 @@
#define HZ_PER_KHZ 1000 #define HZ_PER_KHZ 1000
static struct class *devfreq_class; static struct class *devfreq_class;
static struct dentry *devfreq_debugfs;
/* /*
* devfreq core provides delayed work based load monitoring helper * devfreq core provides delayed work based load monitoring helper
...@@ -209,10 +211,10 @@ static int set_freq_table(struct devfreq *devfreq) ...@@ -209,10 +211,10 @@ static int set_freq_table(struct devfreq *devfreq)
int devfreq_update_status(struct devfreq *devfreq, unsigned long freq) int devfreq_update_status(struct devfreq *devfreq, unsigned long freq)
{ {
int lev, prev_lev, ret = 0; int lev, prev_lev, ret = 0;
unsigned long cur_time; u64 cur_time;
lockdep_assert_held(&devfreq->lock); lockdep_assert_held(&devfreq->lock);
cur_time = jiffies; cur_time = get_jiffies_64();
/* Immediately exit if previous_freq is not initialized yet. */ /* Immediately exit if previous_freq is not initialized yet. */
if (!devfreq->previous_freq) if (!devfreq->previous_freq)
...@@ -224,8 +226,8 @@ int devfreq_update_status(struct devfreq *devfreq, unsigned long freq) ...@@ -224,8 +226,8 @@ int devfreq_update_status(struct devfreq *devfreq, unsigned long freq)
goto out; goto out;
} }
devfreq->time_in_state[prev_lev] += devfreq->stats.time_in_state[prev_lev] +=
cur_time - devfreq->last_stat_updated; cur_time - devfreq->stats.last_update;
lev = devfreq_get_freq_level(devfreq, freq); lev = devfreq_get_freq_level(devfreq, freq);
if (lev < 0) { if (lev < 0) {
...@@ -234,13 +236,13 @@ int devfreq_update_status(struct devfreq *devfreq, unsigned long freq) ...@@ -234,13 +236,13 @@ int devfreq_update_status(struct devfreq *devfreq, unsigned long freq)
} }
if (lev != prev_lev) { if (lev != prev_lev) {
devfreq->trans_table[(prev_lev * devfreq->stats.trans_table[
devfreq->profile->max_state) + lev]++; (prev_lev * devfreq->profile->max_state) + lev]++;
devfreq->total_trans++; devfreq->stats.total_trans++;
} }
out: out:
devfreq->last_stat_updated = cur_time; devfreq->stats.last_update = cur_time;
return ret; return ret;
} }
EXPORT_SYMBOL(devfreq_update_status); EXPORT_SYMBOL(devfreq_update_status);
...@@ -535,7 +537,7 @@ void devfreq_monitor_resume(struct devfreq *devfreq) ...@@ -535,7 +537,7 @@ void devfreq_monitor_resume(struct devfreq *devfreq)
msecs_to_jiffies(devfreq->profile->polling_ms)); msecs_to_jiffies(devfreq->profile->polling_ms));
out_update: out_update:
devfreq->last_stat_updated = jiffies; devfreq->stats.last_update = get_jiffies_64();
devfreq->stop_polling = false; devfreq->stop_polling = false;
if (devfreq->profile->get_cur_freq && if (devfreq->profile->get_cur_freq &&
...@@ -807,28 +809,29 @@ struct devfreq *devfreq_add_device(struct device *dev, ...@@ -807,28 +809,29 @@ struct devfreq *devfreq_add_device(struct device *dev,
goto err_out; goto err_out;
} }
devfreq->trans_table = devm_kzalloc(&devfreq->dev, devfreq->stats.trans_table = devm_kzalloc(&devfreq->dev,
array3_size(sizeof(unsigned int), array3_size(sizeof(unsigned int),
devfreq->profile->max_state, devfreq->profile->max_state,
devfreq->profile->max_state), devfreq->profile->max_state),
GFP_KERNEL); GFP_KERNEL);
if (!devfreq->trans_table) { if (!devfreq->stats.trans_table) {
mutex_unlock(&devfreq->lock); mutex_unlock(&devfreq->lock);
err = -ENOMEM; err = -ENOMEM;
goto err_devfreq; goto err_devfreq;
} }
devfreq->time_in_state = devm_kcalloc(&devfreq->dev, devfreq->stats.time_in_state = devm_kcalloc(&devfreq->dev,
devfreq->profile->max_state, devfreq->profile->max_state,
sizeof(unsigned long), sizeof(*devfreq->stats.time_in_state),
GFP_KERNEL); GFP_KERNEL);
if (!devfreq->time_in_state) { if (!devfreq->stats.time_in_state) {
mutex_unlock(&devfreq->lock); mutex_unlock(&devfreq->lock);
err = -ENOMEM; err = -ENOMEM;
goto err_devfreq; goto err_devfreq;
} }
devfreq->last_stat_updated = jiffies; devfreq->stats.total_trans = 0;
devfreq->stats.last_update = get_jiffies_64();
srcu_init_notifier_head(&devfreq->transition_notifier_list); srcu_init_notifier_head(&devfreq->transition_notifier_list);
...@@ -1259,6 +1262,14 @@ int devfreq_remove_governor(struct devfreq_governor *governor) ...@@ -1259,6 +1262,14 @@ int devfreq_remove_governor(struct devfreq_governor *governor)
} }
EXPORT_SYMBOL(devfreq_remove_governor); EXPORT_SYMBOL(devfreq_remove_governor);
static ssize_t name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct devfreq *devfreq = to_devfreq(dev);
return sprintf(buf, "%s\n", dev_name(devfreq->dev.parent));
}
static DEVICE_ATTR_RO(name);
static ssize_t governor_show(struct device *dev, static ssize_t governor_show(struct device *dev,
struct device_attribute *attr, char *buf) struct device_attribute *attr, char *buf)
{ {
...@@ -1461,6 +1472,7 @@ static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr, ...@@ -1461,6 +1472,7 @@ static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr,
return sprintf(buf, "%lu\n", min_freq); return sprintf(buf, "%lu\n", min_freq);
} }
static DEVICE_ATTR_RW(min_freq);
static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr, static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count) const char *buf, size_t count)
...@@ -1501,7 +1513,6 @@ static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr, ...@@ -1501,7 +1513,6 @@ static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr,
return count; return count;
} }
static DEVICE_ATTR_RW(min_freq);
static ssize_t max_freq_show(struct device *dev, struct device_attribute *attr, static ssize_t max_freq_show(struct device *dev, struct device_attribute *attr,
char *buf) char *buf)
...@@ -1580,18 +1591,47 @@ static ssize_t trans_stat_show(struct device *dev, ...@@ -1580,18 +1591,47 @@ static ssize_t trans_stat_show(struct device *dev,
devfreq->profile->freq_table[i]); devfreq->profile->freq_table[i]);
for (j = 0; j < max_state; j++) for (j = 0; j < max_state; j++)
len += sprintf(buf + len, "%10u", len += sprintf(buf + len, "%10u",
devfreq->trans_table[(i * max_state) + j]); devfreq->stats.trans_table[(i * max_state) + j]);
len += sprintf(buf + len, "%10u\n",
jiffies_to_msecs(devfreq->time_in_state[i])); len += sprintf(buf + len, "%10llu\n", (u64)
jiffies64_to_msecs(devfreq->stats.time_in_state[i]));
} }
len += sprintf(buf + len, "Total transition : %u\n", len += sprintf(buf + len, "Total transition : %u\n",
devfreq->total_trans); devfreq->stats.total_trans);
return len; return len;
} }
static DEVICE_ATTR_RO(trans_stat);
static ssize_t trans_stat_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct devfreq *df = to_devfreq(dev);
int err, value;
if (df->profile->max_state == 0)
return count;
err = kstrtoint(buf, 10, &value);
if (err || value != 0)
return -EINVAL;
mutex_lock(&df->lock);
memset(df->stats.time_in_state, 0, (df->profile->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->stats.total_trans = 0;
df->stats.last_update = get_jiffies_64();
mutex_unlock(&df->lock);
return count;
}
static DEVICE_ATTR_RW(trans_stat);
static struct attribute *devfreq_attrs[] = { static struct attribute *devfreq_attrs[] = {
&dev_attr_name.attr,
&dev_attr_governor.attr, &dev_attr_governor.attr,
&dev_attr_available_governors.attr, &dev_attr_available_governors.attr,
&dev_attr_cur_freq.attr, &dev_attr_cur_freq.attr,
...@@ -1605,6 +1645,81 @@ static struct attribute *devfreq_attrs[] = { ...@@ -1605,6 +1645,81 @@ static struct attribute *devfreq_attrs[] = {
}; };
ATTRIBUTE_GROUPS(devfreq); ATTRIBUTE_GROUPS(devfreq);
/**
* devfreq_summary_show() - Show the summary of the devfreq devices
* @s: seq_file instance to show the summary of devfreq devices
* @data: not used
*
* Show the summary of the devfreq devices via 'devfreq_summary' debugfs file.
* It helps that user can know the detailed information of the devfreq devices.
*
* Return 0 always because it shows the information without any data change.
*/
static int devfreq_summary_show(struct seq_file *s, void *data)
{
struct devfreq *devfreq;
struct devfreq *p_devfreq = NULL;
unsigned long cur_freq, min_freq, max_freq;
unsigned int polling_ms;
seq_printf(s, "%-30s %-10s %-10s %-15s %10s %12s %12s %12s\n",
"dev_name",
"dev",
"parent_dev",
"governor",
"polling_ms",
"cur_freq_Hz",
"min_freq_Hz",
"max_freq_Hz");
seq_printf(s, "%30s %10s %10s %15s %10s %12s %12s %12s\n",
"------------------------------",
"----------",
"----------",
"---------------",
"----------",
"------------",
"------------",
"------------");
mutex_lock(&devfreq_list_lock);
list_for_each_entry_reverse(devfreq, &devfreq_list, node) {
#if IS_ENABLED(CONFIG_DEVFREQ_GOV_PASSIVE)
if (!strncmp(devfreq->governor_name, DEVFREQ_GOV_PASSIVE,
DEVFREQ_NAME_LEN)) {
struct devfreq_passive_data *data = devfreq->data;
if (data)
p_devfreq = data->parent;
} else {
p_devfreq = NULL;
}
#endif
mutex_lock(&devfreq->lock);
cur_freq = devfreq->previous_freq,
get_freq_range(devfreq, &min_freq, &max_freq);
polling_ms = devfreq->profile->polling_ms,
mutex_unlock(&devfreq->lock);
seq_printf(s,
"%-30s %-10s %-10s %-15s %10d %12ld %12ld %12ld\n",
dev_name(devfreq->dev.parent),
dev_name(&devfreq->dev),
p_devfreq ? dev_name(&p_devfreq->dev) : "null",
devfreq->governor_name,
polling_ms,
cur_freq,
min_freq,
max_freq);
}
mutex_unlock(&devfreq_list_lock);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(devfreq_summary);
static int __init devfreq_init(void) static int __init devfreq_init(void)
{ {
devfreq_class = class_create(THIS_MODULE, "devfreq"); devfreq_class = class_create(THIS_MODULE, "devfreq");
...@@ -1621,6 +1736,11 @@ static int __init devfreq_init(void) ...@@ -1621,6 +1736,11 @@ static int __init devfreq_init(void)
} }
devfreq_class->dev_groups = devfreq_groups; devfreq_class->dev_groups = devfreq_groups;
devfreq_debugfs = debugfs_create_dir("devfreq", NULL);
debugfs_create_file("devfreq_summary", 0444,
devfreq_debugfs, NULL,
&devfreq_summary_fops);
return 0; return 0;
} }
subsys_initcall(devfreq_init); subsys_initcall(devfreq_init);
...@@ -1814,7 +1934,7 @@ static void devm_devfreq_notifier_release(struct device *dev, void *res) ...@@ -1814,7 +1934,7 @@ static void devm_devfreq_notifier_release(struct device *dev, void *res)
/** /**
* devm_devfreq_register_notifier() * devm_devfreq_register_notifier()
- Resource-managed devfreq_register_notifier() * - Resource-managed devfreq_register_notifier()
* @dev: The devfreq user device. (parent of devfreq) * @dev: The devfreq user device. (parent of devfreq)
* @devfreq: The devfreq object. * @devfreq: The devfreq object.
* @nb: The notifier block to be unregistered. * @nb: The notifier block to be unregistered.
...@@ -1850,7 +1970,7 @@ EXPORT_SYMBOL(devm_devfreq_register_notifier); ...@@ -1850,7 +1970,7 @@ EXPORT_SYMBOL(devm_devfreq_register_notifier);
/** /**
* devm_devfreq_unregister_notifier() * devm_devfreq_unregister_notifier()
- Resource-managed devfreq_unregister_notifier() * - Resource-managed devfreq_unregister_notifier()
* @dev: The devfreq user device. (parent of devfreq) * @dev: The devfreq user device. (parent of devfreq)
* @devfreq: The devfreq object. * @devfreq: The devfreq object.
* @nb: The notifier block to be unregistered. * @nb: The notifier block to be unregistered.
......
...@@ -15,7 +15,7 @@ menuconfig PM_DEVFREQ_EVENT ...@@ -15,7 +15,7 @@ menuconfig PM_DEVFREQ_EVENT
if PM_DEVFREQ_EVENT if PM_DEVFREQ_EVENT
config DEVFREQ_EVENT_EXYNOS_NOCP config DEVFREQ_EVENT_EXYNOS_NOCP
tristate "EXYNOS NoC (Network On Chip) Probe DEVFREQ event Driver" tristate "Exynos NoC (Network On Chip) Probe DEVFREQ event Driver"
depends on ARCH_EXYNOS || COMPILE_TEST depends on ARCH_EXYNOS || COMPILE_TEST
select PM_OPP select PM_OPP
select REGMAP_MMIO select REGMAP_MMIO
...@@ -24,7 +24,7 @@ config DEVFREQ_EVENT_EXYNOS_NOCP ...@@ -24,7 +24,7 @@ config DEVFREQ_EVENT_EXYNOS_NOCP
(Network on Chip) Probe counters to measure the bandwidth of AXI bus. (Network on Chip) Probe counters to measure the bandwidth of AXI bus.
config DEVFREQ_EVENT_EXYNOS_PPMU config DEVFREQ_EVENT_EXYNOS_PPMU
tristate "EXYNOS PPMU (Platform Performance Monitoring Unit) DEVFREQ event Driver" tristate "Exynos PPMU (Platform Performance Monitoring Unit) DEVFREQ event Driver"
depends on ARCH_EXYNOS || COMPILE_TEST depends on ARCH_EXYNOS || COMPILE_TEST
select PM_OPP select PM_OPP
help help
...@@ -34,7 +34,7 @@ config DEVFREQ_EVENT_EXYNOS_PPMU ...@@ -34,7 +34,7 @@ config DEVFREQ_EVENT_EXYNOS_PPMU
config DEVFREQ_EVENT_ROCKCHIP_DFI config DEVFREQ_EVENT_ROCKCHIP_DFI
tristate "ROCKCHIP DFI DEVFREQ event Driver" tristate "ROCKCHIP DFI DEVFREQ event Driver"
depends on ARCH_ROCKCHIP depends on ARCH_ROCKCHIP || COMPILE_TEST
help help
This add the devfreq-event driver for Rockchip SoC. It provides DFI This add the devfreq-event driver for Rockchip SoC. It provides DFI
(DDR Monitor Module) driver to count ddr load. (DDR Monitor Module) driver to count ddr load.
......
// SPDX-License-Identifier: GPL-2.0-only // SPDX-License-Identifier: GPL-2.0-only
/* /*
* exynos-nocp.c - EXYNOS NoC (Network On Chip) Probe support * exynos-nocp.c - Exynos NoC (Network On Chip) Probe support
* *
* Copyright (c) 2016 Samsung Electronics Co., Ltd. * Copyright (c) 2016 Samsung Electronics Co., Ltd.
* Author : Chanwoo Choi <cw00.choi@samsung.com> * Author : Chanwoo Choi <cw00.choi@samsung.com>
......
/* SPDX-License-Identifier: GPL-2.0-only */ /* SPDX-License-Identifier: GPL-2.0-only */
/* /*
* exynos-nocp.h - EXYNOS NoC (Network on Chip) Probe header file * exynos-nocp.h - Exynos NoC (Network on Chip) Probe header file
* *
* Copyright (c) 2016 Samsung Electronics Co., Ltd. * Copyright (c) 2016 Samsung Electronics Co., Ltd.
* Author : Chanwoo Choi <cw00.choi@samsung.com> * Author : Chanwoo Choi <cw00.choi@samsung.com>
......
// SPDX-License-Identifier: GPL-2.0-only // SPDX-License-Identifier: GPL-2.0-only
/* /*
* exynos_ppmu.c - EXYNOS PPMU (Platform Performance Monitoring Unit) support * exynos_ppmu.c - Exynos PPMU (Platform Performance Monitoring Unit) support
* *
* Copyright (c) 2014-2015 Samsung Electronics Co., Ltd. * Copyright (c) 2014-2015 Samsung Electronics Co., Ltd.
* Author : Chanwoo Choi <cw00.choi@samsung.com> * Author : Chanwoo Choi <cw00.choi@samsung.com>
...@@ -101,17 +101,22 @@ static struct __exynos_ppmu_events { ...@@ -101,17 +101,22 @@ static struct __exynos_ppmu_events {
PPMU_EVENT(dmc1_1), PPMU_EVENT(dmc1_1),
}; };
static int exynos_ppmu_find_ppmu_id(struct devfreq_event_dev *edev) static int __exynos_ppmu_find_ppmu_id(const char *edev_name)
{ {
int i; int i;
for (i = 0; i < ARRAY_SIZE(ppmu_events); i++) for (i = 0; i < ARRAY_SIZE(ppmu_events); i++)
if (!strcmp(edev->desc->name, ppmu_events[i].name)) if (!strcmp(edev_name, ppmu_events[i].name))
return ppmu_events[i].id; return ppmu_events[i].id;
return -EINVAL; return -EINVAL;
} }
static int exynos_ppmu_find_ppmu_id(struct devfreq_event_dev *edev)
{
return __exynos_ppmu_find_ppmu_id(edev->desc->name);
}
/* /*
* The devfreq-event ops structure for PPMU v1.1 * The devfreq-event ops structure for PPMU v1.1
*/ */
...@@ -556,13 +561,11 @@ static int of_get_devfreq_events(struct device_node *np, ...@@ -556,13 +561,11 @@ static int of_get_devfreq_events(struct device_node *np,
* use default if not. * use default if not.
*/ */
if (info->ppmu_type == EXYNOS_TYPE_PPMU_V2) { if (info->ppmu_type == EXYNOS_TYPE_PPMU_V2) {
struct devfreq_event_dev edev;
int id; int id;
/* Not all registers take the same value for /* Not all registers take the same value for
* read+write data count. * read+write data count.
*/ */
edev.desc = &desc[j]; id = __exynos_ppmu_find_ppmu_id(desc[j].name);
id = exynos_ppmu_find_ppmu_id(&edev);
switch (id) { switch (id) {
case PPMU_PMNCNT0: case PPMU_PMNCNT0:
......
/* SPDX-License-Identifier: GPL-2.0-only */ /* SPDX-License-Identifier: GPL-2.0-only */
/* /*
* exynos_ppmu.h - EXYNOS PPMU header file * exynos_ppmu.h - Exynos PPMU header file
* *
* Copyright (c) 2015 Samsung Electronics Co., Ltd. * Copyright (c) 2015 Samsung Electronics Co., Ltd.
* Author : Chanwoo Choi <cw00.choi@samsung.com> * Author : Chanwoo Choi <cw00.choi@samsung.com>
......
...@@ -177,7 +177,6 @@ static int rockchip_dfi_probe(struct platform_device *pdev) ...@@ -177,7 +177,6 @@ static int rockchip_dfi_probe(struct platform_device *pdev)
{ {
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct rockchip_dfi *data; struct rockchip_dfi *data;
struct resource *res;
struct devfreq_event_desc *desc; struct devfreq_event_desc *desc;
struct device_node *np = pdev->dev.of_node, *node; struct device_node *np = pdev->dev.of_node, *node;
...@@ -185,8 +184,7 @@ static int rockchip_dfi_probe(struct platform_device *pdev) ...@@ -185,8 +184,7 @@ static int rockchip_dfi_probe(struct platform_device *pdev)
if (!data) if (!data)
return -ENOMEM; return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); data->regs = devm_platform_ioremap_resource(pdev, 0);
data->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(data->regs)) if (IS_ERR(data->regs))
return PTR_ERR(data->regs); return PTR_ERR(data->regs);
...@@ -200,6 +198,7 @@ static int rockchip_dfi_probe(struct platform_device *pdev) ...@@ -200,6 +198,7 @@ static int rockchip_dfi_probe(struct platform_device *pdev)
node = of_parse_phandle(np, "rockchip,pmu", 0); node = of_parse_phandle(np, "rockchip,pmu", 0);
if (node) { if (node) {
data->regmap_pmu = syscon_node_to_regmap(node); data->regmap_pmu = syscon_node_to_regmap(node);
of_node_put(node);
if (IS_ERR(data->regmap_pmu)) if (IS_ERR(data->regmap_pmu))
return PTR_ERR(data->regmap_pmu); return PTR_ERR(data->regmap_pmu);
} }
......
...@@ -15,11 +15,10 @@ ...@@ -15,11 +15,10 @@
#include <linux/device.h> #include <linux/device.h>
#include <linux/export.h> #include <linux/export.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of_device.h> #include <linux/of.h>
#include <linux/pm_opp.h> #include <linux/pm_opp.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <linux/slab.h>
#define DEFAULT_SATURATION_RATIO 40 #define DEFAULT_SATURATION_RATIO 40
...@@ -127,6 +126,7 @@ static int exynos_bus_get_dev_status(struct device *dev, ...@@ -127,6 +126,7 @@ static int exynos_bus_get_dev_status(struct device *dev,
ret = exynos_bus_get_event(bus, &edata); ret = exynos_bus_get_event(bus, &edata);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "failed to get event from devfreq-event devices\n");
stat->total_time = stat->busy_time = 0; stat->total_time = stat->busy_time = 0;
goto err; goto err;
} }
...@@ -287,52 +287,12 @@ static int exynos_bus_parse_of(struct device_node *np, ...@@ -287,52 +287,12 @@ static int exynos_bus_parse_of(struct device_node *np,
return ret; return ret;
} }
static int exynos_bus_probe(struct platform_device *pdev) static int exynos_bus_profile_init(struct exynos_bus *bus,
struct devfreq_dev_profile *profile)
{ {
struct device *dev = &pdev->dev; struct device *dev = bus->dev;
struct device_node *np = dev->of_node, *node;
struct devfreq_dev_profile *profile;
struct devfreq_simple_ondemand_data *ondemand_data; struct devfreq_simple_ondemand_data *ondemand_data;
struct devfreq_passive_data *passive_data; int ret;
struct devfreq *parent_devfreq;
struct exynos_bus *bus;
int ret, max_state;
unsigned long min_freq, max_freq;
bool passive = false;
if (!np) {
dev_err(dev, "failed to find devicetree node\n");
return -EINVAL;
}
bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
if (!bus)
return -ENOMEM;
mutex_init(&bus->lock);
bus->dev = &pdev->dev;
platform_set_drvdata(pdev, bus);
profile = devm_kzalloc(dev, sizeof(*profile), GFP_KERNEL);
if (!profile)
return -ENOMEM;
node = of_parse_phandle(dev->of_node, "devfreq", 0);
if (node) {
of_node_put(node);
passive = true;
} else {
ret = exynos_bus_parent_parse_of(np, bus);
if (ret < 0)
return ret;
}
/* Parse the device-tree to get the resource information */
ret = exynos_bus_parse_of(np, bus);
if (ret < 0)
goto err_reg;
if (passive)
goto passive;
/* Initialize the struct profile and governor data for parent device */ /* Initialize the struct profile and governor data for parent device */
profile->polling_ms = 50; profile->polling_ms = 50;
...@@ -341,10 +301,9 @@ static int exynos_bus_probe(struct platform_device *pdev) ...@@ -341,10 +301,9 @@ static int exynos_bus_probe(struct platform_device *pdev)
profile->exit = exynos_bus_exit; profile->exit = exynos_bus_exit;
ondemand_data = devm_kzalloc(dev, sizeof(*ondemand_data), GFP_KERNEL); ondemand_data = devm_kzalloc(dev, sizeof(*ondemand_data), GFP_KERNEL);
if (!ondemand_data) { if (!ondemand_data)
ret = -ENOMEM; return -ENOMEM;
goto err;
}
ondemand_data->upthreshold = 40; ondemand_data->upthreshold = 40;
ondemand_data->downdifferential = 5; ondemand_data->downdifferential = 5;
...@@ -354,15 +313,14 @@ static int exynos_bus_probe(struct platform_device *pdev) ...@@ -354,15 +313,14 @@ static int exynos_bus_probe(struct platform_device *pdev)
ondemand_data); ondemand_data);
if (IS_ERR(bus->devfreq)) { if (IS_ERR(bus->devfreq)) {
dev_err(dev, "failed to add devfreq device\n"); dev_err(dev, "failed to add devfreq device\n");
ret = PTR_ERR(bus->devfreq); return PTR_ERR(bus->devfreq);
goto err;
} }
/* Register opp_notifier to catch the change of OPP */ /* Register opp_notifier to catch the change of OPP */
ret = devm_devfreq_register_opp_notifier(dev, bus->devfreq); ret = devm_devfreq_register_opp_notifier(dev, bus->devfreq);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "failed to register opp notifier\n"); dev_err(dev, "failed to register opp notifier\n");
goto err; return ret;
} }
/* /*
...@@ -372,33 +330,44 @@ static int exynos_bus_probe(struct platform_device *pdev) ...@@ -372,33 +330,44 @@ static int exynos_bus_probe(struct platform_device *pdev)
ret = exynos_bus_enable_edev(bus); ret = exynos_bus_enable_edev(bus);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "failed to enable devfreq-event devices\n"); dev_err(dev, "failed to enable devfreq-event devices\n");
goto err; return ret;
} }
ret = exynos_bus_set_event(bus); ret = exynos_bus_set_event(bus);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "failed to set event to devfreq-event devices\n"); dev_err(dev, "failed to set event to devfreq-event devices\n");
goto err; goto err_edev;
} }
goto out; return 0;
passive:
err_edev:
if (exynos_bus_disable_edev(bus))
dev_warn(dev, "failed to disable the devfreq-event devices\n");
return ret;
}
static int exynos_bus_profile_init_passive(struct exynos_bus *bus,
struct devfreq_dev_profile *profile)
{
struct device *dev = bus->dev;
struct devfreq_passive_data *passive_data;
struct devfreq *parent_devfreq;
/* Initialize the struct profile and governor data for passive device */ /* Initialize the struct profile and governor data for passive device */
profile->target = exynos_bus_target; profile->target = exynos_bus_target;
profile->exit = exynos_bus_passive_exit; profile->exit = exynos_bus_passive_exit;
/* Get the instance of parent devfreq device */ /* Get the instance of parent devfreq device */
parent_devfreq = devfreq_get_devfreq_by_phandle(dev, 0); parent_devfreq = devfreq_get_devfreq_by_phandle(dev, 0);
if (IS_ERR(parent_devfreq)) { if (IS_ERR(parent_devfreq))
ret = -EPROBE_DEFER; return -EPROBE_DEFER;
goto err;
}
passive_data = devm_kzalloc(dev, sizeof(*passive_data), GFP_KERNEL); passive_data = devm_kzalloc(dev, sizeof(*passive_data), GFP_KERNEL);
if (!passive_data) { if (!passive_data)
ret = -ENOMEM; return -ENOMEM;
goto err;
}
passive_data->parent = parent_devfreq; passive_data->parent = parent_devfreq;
/* Add devfreq device for exynos bus with passive governor */ /* Add devfreq device for exynos bus with passive governor */
...@@ -407,11 +376,61 @@ static int exynos_bus_probe(struct platform_device *pdev) ...@@ -407,11 +376,61 @@ static int exynos_bus_probe(struct platform_device *pdev)
if (IS_ERR(bus->devfreq)) { if (IS_ERR(bus->devfreq)) {
dev_err(dev, dev_err(dev,
"failed to add devfreq dev with passive governor\n"); "failed to add devfreq dev with passive governor\n");
ret = PTR_ERR(bus->devfreq); return PTR_ERR(bus->devfreq);
goto err; }
return 0;
}
static int exynos_bus_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node, *node;
struct devfreq_dev_profile *profile;
struct exynos_bus *bus;
int ret, max_state;
unsigned long min_freq, max_freq;
bool passive = false;
if (!np) {
dev_err(dev, "failed to find devicetree node\n");
return -EINVAL;
} }
out: bus = devm_kzalloc(&pdev->dev, sizeof(*bus), GFP_KERNEL);
if (!bus)
return -ENOMEM;
mutex_init(&bus->lock);
bus->dev = &pdev->dev;
platform_set_drvdata(pdev, bus);
profile = devm_kzalloc(dev, sizeof(*profile), GFP_KERNEL);
if (!profile)
return -ENOMEM;
node = of_parse_phandle(dev->of_node, "devfreq", 0);
if (node) {
of_node_put(node);
passive = true;
} else {
ret = exynos_bus_parent_parse_of(np, bus);
if (ret < 0)
return ret;
}
/* Parse the device-tree to get the resource information */
ret = exynos_bus_parse_of(np, bus);
if (ret < 0)
goto err_reg;
if (passive)
ret = exynos_bus_profile_init_passive(bus, profile);
else
ret = exynos_bus_profile_init(bus, profile);
if (ret < 0)
goto err;
max_state = bus->devfreq->profile->max_state; max_state = bus->devfreq->profile->max_state;
min_freq = (bus->devfreq->profile->freq_table[0] / 1000); min_freq = (bus->devfreq->profile->freq_table[0] / 1000);
max_freq = (bus->devfreq->profile->freq_table[max_state - 1] / 1000); max_freq = (bus->devfreq->profile->freq_table[max_state - 1] / 1000);
......
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019 NXP
*/
#include <linux/module.h>
#include <linux/device.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/devfreq.h>
#include <linux/pm_opp.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/arm-smccc.h>
#define IMX_SIP_DDR_DVFS 0xc2000004
/* Query available frequencies. */
#define IMX_SIP_DDR_DVFS_GET_FREQ_COUNT 0x10
#define IMX_SIP_DDR_DVFS_GET_FREQ_INFO 0x11
/*
* This should be in a 1:1 mapping with devicetree OPPs but
* firmware provides additional info.
*/
struct imx8m_ddrc_freq {
unsigned long rate;
unsigned long smcarg;
int dram_core_parent_index;
int dram_alt_parent_index;
int dram_apb_parent_index;
};
/* Hardware limitation */
#define IMX8M_DDRC_MAX_FREQ_COUNT 4
/*
* i.MX8M DRAM Controller clocks have the following structure (abridged):
*
* +----------+ |\ +------+
* | dram_pll |-------|M| dram_core | |
* +----------+ |U|---------->| D |
* /--|X| | D |
* dram_alt_root | |/ | R |
* | | C |
* +---------+ | |
* |FIX DIV/4| | |
* +---------+ | |
* composite: | | |
* +----------+ | | |
* | dram_alt |----/ | |
* +----------+ | |
* | dram_apb |-------------------->| |
* +----------+ +------+
*
* The dram_pll is used for higher rates and dram_alt is used for lower rates.
*
* Frequency switching is implemented in TF-A (via SMC call) and can change the
* configuration of the clocks, including mux parents. The dram_alt and
* dram_apb clocks are "imx composite" and their parent can change too.
*
* We need to prepare/enable the new mux parents head of switching and update
* their information afterwards.
*/
struct imx8m_ddrc {
struct devfreq_dev_profile profile;
struct devfreq *devfreq;
/* For frequency switching: */
struct clk *dram_core;
struct clk *dram_pll;
struct clk *dram_alt;
struct clk *dram_apb;
int freq_count;
struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
};
static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv,
unsigned long rate)
{
struct imx8m_ddrc_freq *freq;
int i;
/*
* Firmware reports values in MT/s, so we round-down from Hz
* Rounding is extra generous to ensure a match.
*/
rate = DIV_ROUND_CLOSEST(rate, 250000);
for (i = 0; i < priv->freq_count; ++i) {
freq = &priv->freq_table[i];
if (freq->rate == rate ||
freq->rate + 1 == rate ||
freq->rate - 1 == rate)
return freq;
}
return NULL;
}
static void imx8m_ddrc_smc_set_freq(int target_freq)
{
struct arm_smccc_res res;
u32 online_cpus = 0;
int cpu;
local_irq_disable();
for_each_online_cpu(cpu)
online_cpus |= (1 << (cpu * 8));
/* change the ddr freqency */
arm_smccc_smc(IMX_SIP_DDR_DVFS, target_freq, online_cpus,
0, 0, 0, 0, 0, &res);
local_irq_enable();
}
static struct clk *clk_get_parent_by_index(struct clk *clk, int index)
{
struct clk_hw *hw;
hw = clk_hw_get_parent_by_index(__clk_get_hw(clk), index);
return hw ? hw->clk : NULL;
}
static int imx8m_ddrc_set_freq(struct device *dev, struct imx8m_ddrc_freq *freq)
{
struct imx8m_ddrc *priv = dev_get_drvdata(dev);
struct clk *new_dram_core_parent;
struct clk *new_dram_alt_parent;
struct clk *new_dram_apb_parent;
int ret;
/*
* Fetch new parents
*
* new_dram_alt_parent and new_dram_apb_parent are optional but
* new_dram_core_parent is not.
*/
new_dram_core_parent = clk_get_parent_by_index(
priv->dram_core, freq->dram_core_parent_index - 1);
if (!new_dram_core_parent) {
dev_err(dev, "failed to fetch new dram_core parent\n");
return -EINVAL;
}
if (freq->dram_alt_parent_index) {
new_dram_alt_parent = clk_get_parent_by_index(
priv->dram_alt,
freq->dram_alt_parent_index - 1);
if (!new_dram_alt_parent) {
dev_err(dev, "failed to fetch new dram_alt parent\n");
return -EINVAL;
}
} else
new_dram_alt_parent = NULL;
if (freq->dram_apb_parent_index) {
new_dram_apb_parent = clk_get_parent_by_index(
priv->dram_apb,
freq->dram_apb_parent_index - 1);
if (!new_dram_apb_parent) {
dev_err(dev, "failed to fetch new dram_apb parent\n");
return -EINVAL;
}
} else
new_dram_apb_parent = NULL;
/* increase reference counts and ensure clks are ON before switch */
ret = clk_prepare_enable(new_dram_core_parent);
if (ret) {
dev_err(dev, "failed to enable new dram_core parent: %d\n",
ret);
goto out;
}
ret = clk_prepare_enable(new_dram_alt_parent);
if (ret) {
dev_err(dev, "failed to enable new dram_alt parent: %d\n",
ret);
goto out_disable_core_parent;
}
ret = clk_prepare_enable(new_dram_apb_parent);
if (ret) {
dev_err(dev, "failed to enable new dram_apb parent: %d\n",
ret);
goto out_disable_alt_parent;
}
imx8m_ddrc_smc_set_freq(freq->smcarg);
/* update parents in clk tree after switch. */
ret = clk_set_parent(priv->dram_core, new_dram_core_parent);
if (ret)
dev_warn(dev, "failed to set dram_core parent: %d\n", ret);
if (new_dram_alt_parent) {
ret = clk_set_parent(priv->dram_alt, new_dram_alt_parent);
if (ret)
dev_warn(dev, "failed to set dram_alt parent: %d\n",
ret);
}
if (new_dram_apb_parent) {
ret = clk_set_parent(priv->dram_apb, new_dram_apb_parent);
if (ret)
dev_warn(dev, "failed to set dram_apb parent: %d\n",
ret);
}
/*
* Explicitly refresh dram PLL rate.
*
* Even if it's marked with CLK_GET_RATE_NOCACHE the rate will not be
* automatically refreshed when clk_get_rate is called on children.
*/
clk_get_rate(priv->dram_pll);
/*
* clk_set_parent transfer the reference count from old parent.
* now we drop extra reference counts used during the switch
*/
clk_disable_unprepare(new_dram_apb_parent);
out_disable_alt_parent:
clk_disable_unprepare(new_dram_alt_parent);
out_disable_core_parent:
clk_disable_unprepare(new_dram_core_parent);
out:
return ret;
}
static int imx8m_ddrc_target(struct device *dev, unsigned long *freq, u32 flags)
{
struct imx8m_ddrc *priv = dev_get_drvdata(dev);
struct imx8m_ddrc_freq *freq_info;
struct dev_pm_opp *new_opp;
unsigned long old_freq, new_freq;
int ret;
new_opp = devfreq_recommended_opp(dev, freq, flags);
if (IS_ERR(new_opp)) {
ret = PTR_ERR(new_opp);
dev_err(dev, "failed to get recommended opp: %d\n", ret);
return ret;
}
dev_pm_opp_put(new_opp);
old_freq = clk_get_rate(priv->dram_core);
if (*freq == old_freq)
return 0;
freq_info = imx8m_ddrc_find_freq(priv, *freq);
if (!freq_info)
return -EINVAL;
/*
* Read back the clk rate to verify switch was correct and so that
* we can report it on all error paths.
*/
ret = imx8m_ddrc_set_freq(dev, freq_info);
new_freq = clk_get_rate(priv->dram_core);
if (ret)
dev_err(dev, "ddrc failed freq switch to %lu from %lu: error %d. now at %lu\n",
*freq, old_freq, ret, new_freq);
else if (*freq != new_freq)
dev_err(dev, "ddrc failed freq update to %lu from %lu, now at %lu\n",
*freq, old_freq, new_freq);
else
dev_dbg(dev, "ddrc freq set to %lu (was %lu)\n",
*freq, old_freq);
return ret;
}
static int imx8m_ddrc_get_cur_freq(struct device *dev, unsigned long *freq)
{
struct imx8m_ddrc *priv = dev_get_drvdata(dev);
*freq = clk_get_rate(priv->dram_core);
return 0;
}
static int imx8m_ddrc_get_dev_status(struct device *dev,
struct devfreq_dev_status *stat)
{
struct imx8m_ddrc *priv = dev_get_drvdata(dev);
stat->busy_time = 0;
stat->total_time = 0;
stat->current_frequency = clk_get_rate(priv->dram_core);
return 0;
}
static int imx8m_ddrc_init_freq_info(struct device *dev)
{
struct imx8m_ddrc *priv = dev_get_drvdata(dev);
struct arm_smccc_res res;
int index;
/* An error here means DDR DVFS API not supported by firmware */
arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_COUNT,
0, 0, 0, 0, 0, 0, &res);
priv->freq_count = res.a0;
if (priv->freq_count <= 0 ||
priv->freq_count > IMX8M_DDRC_MAX_FREQ_COUNT)
return -ENODEV;
for (index = 0; index < priv->freq_count; ++index) {
struct imx8m_ddrc_freq *freq = &priv->freq_table[index];
arm_smccc_smc(IMX_SIP_DDR_DVFS, IMX_SIP_DDR_DVFS_GET_FREQ_INFO,
index, 0, 0, 0, 0, 0, &res);
/* Result should be strictly positive */
if ((long)res.a0 <= 0)
return -ENODEV;
freq->rate = res.a0;
freq->smcarg = index;
freq->dram_core_parent_index = res.a1;
freq->dram_alt_parent_index = res.a2;
freq->dram_apb_parent_index = res.a3;
/* dram_core has 2 options: dram_pll or dram_alt_root */
if (freq->dram_core_parent_index != 1 &&
freq->dram_core_parent_index != 2)
return -ENODEV;
/* dram_apb and dram_alt have exactly 8 possible parents */
if (freq->dram_alt_parent_index > 8 ||
freq->dram_apb_parent_index > 8)
return -ENODEV;
/* dram_core from alt requires explicit dram_alt parent */
if (freq->dram_core_parent_index == 2 &&
freq->dram_alt_parent_index == 0)
return -ENODEV;
}
return 0;
}
static int imx8m_ddrc_check_opps(struct device *dev)
{
struct imx8m_ddrc *priv = dev_get_drvdata(dev);
struct imx8m_ddrc_freq *freq_info;
struct dev_pm_opp *opp;
unsigned long freq;
int i, opp_count;
/* Enumerate DT OPPs and disable those not supported by firmware */
opp_count = dev_pm_opp_get_opp_count(dev);
if (opp_count < 0)
return opp_count;
for (i = 0, freq = 0; i < opp_count; ++i, ++freq) {
opp = dev_pm_opp_find_freq_ceil(dev, &freq);
if (IS_ERR(opp)) {
dev_err(dev, "Failed enumerating OPPs: %ld\n",
PTR_ERR(opp));
return PTR_ERR(opp);
}
dev_pm_opp_put(opp);
freq_info = imx8m_ddrc_find_freq(priv, freq);
if (!freq_info) {
dev_info(dev, "Disable unsupported OPP %luHz %luMT/s\n",
freq, DIV_ROUND_CLOSEST(freq, 250000));
dev_pm_opp_disable(dev, freq);
}
}
return 0;
}
static void imx8m_ddrc_exit(struct device *dev)
{
dev_pm_opp_of_remove_table(dev);
}
static int imx8m_ddrc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct imx8m_ddrc *priv;
const char *gov = DEVFREQ_GOV_USERSPACE;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
ret = imx8m_ddrc_init_freq_info(dev);
if (ret) {
dev_err(dev, "failed to init firmware freq info: %d\n", ret);
return ret;
}
priv->dram_core = devm_clk_get(dev, "core");
if (IS_ERR(priv->dram_core)) {
ret = PTR_ERR(priv->dram_core);
dev_err(dev, "failed to fetch core clock: %d\n", ret);
return ret;
}
priv->dram_pll = devm_clk_get(dev, "pll");
if (IS_ERR(priv->dram_pll)) {
ret = PTR_ERR(priv->dram_pll);
dev_err(dev, "failed to fetch pll clock: %d\n", ret);
return ret;
}
priv->dram_alt = devm_clk_get(dev, "alt");
if (IS_ERR(priv->dram_alt)) {
ret = PTR_ERR(priv->dram_alt);
dev_err(dev, "failed to fetch alt clock: %d\n", ret);
return ret;
}
priv->dram_apb = devm_clk_get(dev, "apb");
if (IS_ERR(priv->dram_apb)) {
ret = PTR_ERR(priv->dram_apb);
dev_err(dev, "failed to fetch apb clock: %d\n", ret);
return ret;
}
ret = dev_pm_opp_of_add_table(dev);
if (ret < 0) {
dev_err(dev, "failed to get OPP table\n");
return ret;
}
ret = imx8m_ddrc_check_opps(dev);
if (ret < 0)
goto err;
priv->profile.polling_ms = 1000;
priv->profile.target = imx8m_ddrc_target;
priv->profile.get_dev_status = imx8m_ddrc_get_dev_status;
priv->profile.exit = imx8m_ddrc_exit;
priv->profile.get_cur_freq = imx8m_ddrc_get_cur_freq;
priv->profile.initial_freq = clk_get_rate(priv->dram_core);
priv->devfreq = devm_devfreq_add_device(dev, &priv->profile,
gov, NULL);
if (IS_ERR(priv->devfreq)) {
ret = PTR_ERR(priv->devfreq);
dev_err(dev, "failed to add devfreq device: %d\n", ret);
goto err;
}
return 0;
err:
dev_pm_opp_of_remove_table(dev);
return ret;
}
static const struct of_device_id imx8m_ddrc_of_match[] = {
{ .compatible = "fsl,imx8m-ddrc", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, imx8m_ddrc_of_match);
static struct platform_driver imx8m_ddrc_platdrv = {
.probe = imx8m_ddrc_probe,
.driver = {
.name = "imx8m-ddrc-devfreq",
.of_match_table = of_match_ptr(imx8m_ddrc_of_match),
},
};
module_platform_driver(imx8m_ddrc_platdrv);
MODULE_DESCRIPTION("i.MX8M DDR Controller frequency driver");
MODULE_AUTHOR("Leonard Crestez <leonard.crestez@nxp.com>");
MODULE_LICENSE("GPL v2");
...@@ -364,7 +364,8 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev) ...@@ -364,7 +364,8 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
if (res.a0) { if (res.a0) {
dev_err(dev, "Failed to set dram param: %ld\n", dev_err(dev, "Failed to set dram param: %ld\n",
res.a0); res.a0);
return -EINVAL; ret = -EINVAL;
goto err_edev;
} }
} }
} }
...@@ -372,8 +373,11 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev) ...@@ -372,8 +373,11 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
node = of_parse_phandle(np, "rockchip,pmu", 0); node = of_parse_phandle(np, "rockchip,pmu", 0);
if (node) { if (node) {
data->regmap_pmu = syscon_node_to_regmap(node); data->regmap_pmu = syscon_node_to_regmap(node);
if (IS_ERR(data->regmap_pmu)) of_node_put(node);
return PTR_ERR(data->regmap_pmu); if (IS_ERR(data->regmap_pmu)) {
ret = PTR_ERR(data->regmap_pmu);
goto err_edev;
}
} }
regmap_read(data->regmap_pmu, RK3399_PMUGRF_OS_REG2, &val); regmap_read(data->regmap_pmu, RK3399_PMUGRF_OS_REG2, &val);
...@@ -391,7 +395,8 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev) ...@@ -391,7 +395,8 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
data->odt_dis_freq = data->timing.lpddr4_odt_dis_freq; data->odt_dis_freq = data->timing.lpddr4_odt_dis_freq;
break; break;
default: default:
return -EINVAL; ret = -EINVAL;
goto err_edev;
}; };
arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, 0, 0, arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, 0, 0,
...@@ -425,7 +430,8 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev) ...@@ -425,7 +430,8 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
*/ */
if (dev_pm_opp_of_add_table(dev)) { if (dev_pm_opp_of_add_table(dev)) {
dev_err(dev, "Invalid operating-points in device tree.\n"); dev_err(dev, "Invalid operating-points in device tree.\n");
return -EINVAL; ret = -EINVAL;
goto err_edev;
} }
of_property_read_u32(np, "upthreshold", of_property_read_u32(np, "upthreshold",
...@@ -465,6 +471,9 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev) ...@@ -465,6 +471,9 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
err_free_opp: err_free_opp:
dev_pm_opp_of_remove_table(&pdev->dev); dev_pm_opp_of_remove_table(&pdev->dev);
err_edev:
devfreq_event_disable_edev(data->edev);
return ret; return ret;
} }
......
...@@ -107,6 +107,20 @@ struct devfreq_dev_profile { ...@@ -107,6 +107,20 @@ struct devfreq_dev_profile {
unsigned int max_state; unsigned int max_state;
}; };
/**
* struct devfreq_stats - Statistics of devfreq device behavior
* @total_trans: Number of devfreq transitions.
* @trans_table: Statistics of devfreq transitions.
* @time_in_state: Statistics of devfreq states.
* @last_update: The last time stats were updated.
*/
struct devfreq_stats {
unsigned int total_trans;
unsigned int *trans_table;
u64 *time_in_state;
u64 last_update;
};
/** /**
* struct devfreq - Device devfreq structure * struct devfreq - Device devfreq structure
* @node: list node - contains the devices with devfreq that have been * @node: list node - contains the devices with devfreq that have been
...@@ -122,6 +136,7 @@ struct devfreq_dev_profile { ...@@ -122,6 +136,7 @@ struct devfreq_dev_profile {
* devfreq.nb to the corresponding register notifier call chain. * devfreq.nb to the corresponding register notifier call chain.
* @work: delayed work for load monitoring. * @work: delayed work for load monitoring.
* @previous_freq: previously configured frequency value. * @previous_freq: previously configured frequency value.
* @last_status: devfreq user device info, performance statistics
* @data: Private data of the governor. The devfreq framework does not * @data: Private data of the governor. The devfreq framework does not
* touch this. * touch this.
* @user_min_freq_req: PM QoS minimum frequency request from user (via sysfs) * @user_min_freq_req: PM QoS minimum frequency request from user (via sysfs)
...@@ -132,15 +147,12 @@ struct devfreq_dev_profile { ...@@ -132,15 +147,12 @@ struct devfreq_dev_profile {
* @suspend_freq: frequency of a device set during suspend phase. * @suspend_freq: frequency of a device set during suspend phase.
* @resume_freq: frequency of a device set in resume phase. * @resume_freq: frequency of a device set in resume phase.
* @suspend_count: suspend requests counter for a device. * @suspend_count: suspend requests counter for a device.
* @total_trans: Number of devfreq transitions * @stats: Statistics of devfreq device behavior
* @trans_table: Statistics of devfreq transitions
* @time_in_state: Statistics of devfreq states
* @last_stat_updated: The last time stat updated
* @transition_notifier_list: list head of DEVFREQ_TRANSITION_NOTIFIER notifier * @transition_notifier_list: list head of DEVFREQ_TRANSITION_NOTIFIER notifier
* @nb_min: Notifier block for DEV_PM_QOS_MIN_FREQUENCY * @nb_min: Notifier block for DEV_PM_QOS_MIN_FREQUENCY
* @nb_max: Notifier block for DEV_PM_QOS_MAX_FREQUENCY * @nb_max: Notifier block for DEV_PM_QOS_MAX_FREQUENCY
* *
* This structure stores the devfreq information for a give device. * This structure stores the devfreq information for a given device.
* *
* Note that when a governor accesses entries in struct devfreq in its * Note that when a governor accesses entries in struct devfreq in its
* functions except for the context of callbacks defined in struct * functions except for the context of callbacks defined in struct
...@@ -174,11 +186,8 @@ struct devfreq { ...@@ -174,11 +186,8 @@ struct devfreq {
unsigned long resume_freq; unsigned long resume_freq;
atomic_t suspend_count; atomic_t suspend_count;
/* information for device frequency transition */ /* information for device frequency transitions */
unsigned int total_trans; struct devfreq_stats stats;
unsigned int *trans_table;
unsigned long *time_in_state;
unsigned long last_stat_updated;
struct srcu_notifier_head transition_notifier_list; struct srcu_notifier_head transition_notifier_list;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment