mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-04 18:09:57 +00:00
Merge branch 'pm-devfreq'
* pm-devfreq: (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 ...
This commit is contained in:
commit
c102671af0
16 changed files with 870 additions and 146 deletions
|
@ -7,6 +7,13 @@ Description:
|
|||
The name of devfreq object denoted as ... is same as the
|
||||
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
|
||||
Date: September 2011
|
||||
Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
|
||||
|
@ -48,12 +55,15 @@ What: /sys/class/devfreq/.../trans_stat
|
|||
Date: October 2012
|
||||
Contact: MyungJoo Ham <myungjoo.ham@samsung.com>
|
||||
Description:
|
||||
This ABI shows the statistics of devfreq behavior on a
|
||||
specific device. It shows the time spent in each state and
|
||||
the number of transitions between states.
|
||||
This ABI shows or clears the statistics of devfreq behavior
|
||||
on a specific device. It shows the time spent in each state
|
||||
and the number of transitions between states.
|
||||
In order to activate this ABI, the devfreq target device
|
||||
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
|
||||
Date: September 2011
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# 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
|
|||
comment "DEVFREQ Drivers"
|
||||
|
||||
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
|
||||
select DEVFREQ_GOV_SIMPLE_ONDEMAND
|
||||
select DEVFREQ_GOV_PASSIVE
|
||||
|
@ -91,6 +91,16 @@ config ARM_EXYNOS_BUS_DEVFREQ
|
|||
and adjusts the operating frequencies and voltages with OPP support.
|
||||
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
|
||||
tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
|
||||
depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
|
||||
|
@ -115,14 +125,15 @@ config ARM_TEGRA20_DEVFREQ
|
|||
|
||||
config ARM_RK3399_DMC_DEVFREQ
|
||||
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_GOV_SIMPLE_ONDEMAND
|
||||
select PM_DEVFREQ_EVENT
|
||||
help
|
||||
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
|
||||
from hardware.
|
||||
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
|
||||
from hardware.
|
||||
|
||||
source "drivers/devfreq/event/Kconfig"
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o
|
|||
|
||||
# DEVFREQ Drivers
|
||||
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_TEGRA_DEVFREQ) += tegra30-devfreq.o
|
||||
obj-$(CONFIG_ARM_TEGRA20_DEVFREQ) += tegra20-devfreq.o
|
||||
|
|
|
@ -346,9 +346,9 @@ EXPORT_SYMBOL_GPL(devfreq_event_add_edev);
|
|||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/kmod.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
|
@ -33,6 +34,7 @@
|
|||
#define HZ_PER_KHZ 1000
|
||||
|
||||
static struct class *devfreq_class;
|
||||
static struct dentry *devfreq_debugfs;
|
||||
|
||||
/*
|
||||
* devfreq core provides delayed work based load monitoring helper
|
||||
|
@ -209,10 +211,10 @@ static int set_freq_table(struct devfreq *devfreq)
|
|||
int devfreq_update_status(struct devfreq *devfreq, unsigned long freq)
|
||||
{
|
||||
int lev, prev_lev, ret = 0;
|
||||
unsigned long cur_time;
|
||||
u64 cur_time;
|
||||
|
||||
lockdep_assert_held(&devfreq->lock);
|
||||
cur_time = jiffies;
|
||||
cur_time = get_jiffies_64();
|
||||
|
||||
/* Immediately exit if previous_freq is not initialized yet. */
|
||||
if (!devfreq->previous_freq)
|
||||
|
@ -224,8 +226,8 @@ int devfreq_update_status(struct devfreq *devfreq, unsigned long freq)
|
|||
goto out;
|
||||
}
|
||||
|
||||
devfreq->time_in_state[prev_lev] +=
|
||||
cur_time - devfreq->last_stat_updated;
|
||||
devfreq->stats.time_in_state[prev_lev] +=
|
||||
cur_time - devfreq->stats.last_update;
|
||||
|
||||
lev = devfreq_get_freq_level(devfreq, freq);
|
||||
if (lev < 0) {
|
||||
|
@ -234,13 +236,13 @@ int devfreq_update_status(struct devfreq *devfreq, unsigned long freq)
|
|||
}
|
||||
|
||||
if (lev != prev_lev) {
|
||||
devfreq->trans_table[(prev_lev *
|
||||
devfreq->profile->max_state) + lev]++;
|
||||
devfreq->total_trans++;
|
||||
devfreq->stats.trans_table[
|
||||
(prev_lev * devfreq->profile->max_state) + lev]++;
|
||||
devfreq->stats.total_trans++;
|
||||
}
|
||||
|
||||
out:
|
||||
devfreq->last_stat_updated = cur_time;
|
||||
devfreq->stats.last_update = cur_time;
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(devfreq_update_status);
|
||||
|
@ -535,7 +537,7 @@ void devfreq_monitor_resume(struct devfreq *devfreq)
|
|||
msecs_to_jiffies(devfreq->profile->polling_ms));
|
||||
|
||||
out_update:
|
||||
devfreq->last_stat_updated = jiffies;
|
||||
devfreq->stats.last_update = get_jiffies_64();
|
||||
devfreq->stop_polling = false;
|
||||
|
||||
if (devfreq->profile->get_cur_freq &&
|
||||
|
@ -807,28 +809,29 @@ struct devfreq *devfreq_add_device(struct device *dev,
|
|||
goto err_out;
|
||||
}
|
||||
|
||||
devfreq->trans_table = devm_kzalloc(&devfreq->dev,
|
||||
devfreq->stats.trans_table = devm_kzalloc(&devfreq->dev,
|
||||
array3_size(sizeof(unsigned int),
|
||||
devfreq->profile->max_state,
|
||||
devfreq->profile->max_state),
|
||||
GFP_KERNEL);
|
||||
if (!devfreq->trans_table) {
|
||||
if (!devfreq->stats.trans_table) {
|
||||
mutex_unlock(&devfreq->lock);
|
||||
err = -ENOMEM;
|
||||
goto err_devfreq;
|
||||
}
|
||||
|
||||
devfreq->time_in_state = devm_kcalloc(&devfreq->dev,
|
||||
devfreq->stats.time_in_state = devm_kcalloc(&devfreq->dev,
|
||||
devfreq->profile->max_state,
|
||||
sizeof(unsigned long),
|
||||
sizeof(*devfreq->stats.time_in_state),
|
||||
GFP_KERNEL);
|
||||
if (!devfreq->time_in_state) {
|
||||
if (!devfreq->stats.time_in_state) {
|
||||
mutex_unlock(&devfreq->lock);
|
||||
err = -ENOMEM;
|
||||
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);
|
||||
|
||||
|
@ -1259,6 +1262,14 @@ int devfreq_remove_governor(struct devfreq_governor *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,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
|
@ -1461,6 +1472,7 @@ static ssize_t min_freq_show(struct device *dev, struct device_attribute *attr,
|
|||
|
||||
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,
|
||||
const char *buf, size_t count)
|
||||
|
@ -1501,7 +1513,6 @@ static ssize_t max_freq_store(struct device *dev, struct device_attribute *attr,
|
|||
|
||||
return count;
|
||||
}
|
||||
static DEVICE_ATTR_RW(min_freq);
|
||||
|
||||
static ssize_t max_freq_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
|
@ -1580,18 +1591,47 @@ static ssize_t trans_stat_show(struct device *dev,
|
|||
devfreq->profile->freq_table[i]);
|
||||
for (j = 0; j < max_state; j++)
|
||||
len += sprintf(buf + len, "%10u",
|
||||
devfreq->trans_table[(i * max_state) + j]);
|
||||
len += sprintf(buf + len, "%10u\n",
|
||||
jiffies_to_msecs(devfreq->time_in_state[i]));
|
||||
devfreq->stats.trans_table[(i * max_state) + j]);
|
||||
|
||||
len += sprintf(buf + len, "%10llu\n", (u64)
|
||||
jiffies64_to_msecs(devfreq->stats.time_in_state[i]));
|
||||
}
|
||||
|
||||
len += sprintf(buf + len, "Total transition : %u\n",
|
||||
devfreq->total_trans);
|
||||
devfreq->stats.total_trans);
|
||||
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[] = {
|
||||
&dev_attr_name.attr,
|
||||
&dev_attr_governor.attr,
|
||||
&dev_attr_available_governors.attr,
|
||||
&dev_attr_cur_freq.attr,
|
||||
|
@ -1605,6 +1645,81 @@ static struct attribute *devfreq_attrs[] = {
|
|||
};
|
||||
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)
|
||||
{
|
||||
devfreq_class = class_create(THIS_MODULE, "devfreq");
|
||||
|
@ -1621,6 +1736,11 @@ static int __init devfreq_init(void)
|
|||
}
|
||||
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;
|
||||
}
|
||||
subsys_initcall(devfreq_init);
|
||||
|
@ -1814,7 +1934,7 @@ static void devm_devfreq_notifier_release(struct device *dev, void *res)
|
|||
|
||||
/**
|
||||
* devm_devfreq_register_notifier()
|
||||
- Resource-managed devfreq_register_notifier()
|
||||
* - Resource-managed devfreq_register_notifier()
|
||||
* @dev: The devfreq user device. (parent of devfreq)
|
||||
* @devfreq: The devfreq object.
|
||||
* @nb: The notifier block to be unregistered.
|
||||
|
@ -1850,7 +1970,7 @@ EXPORT_SYMBOL(devm_devfreq_register_notifier);
|
|||
|
||||
/**
|
||||
* devm_devfreq_unregister_notifier()
|
||||
- Resource-managed devfreq_unregister_notifier()
|
||||
* - Resource-managed devfreq_unregister_notifier()
|
||||
* @dev: The devfreq user device. (parent of devfreq)
|
||||
* @devfreq: The devfreq object.
|
||||
* @nb: The notifier block to be unregistered.
|
||||
|
|
|
@ -15,7 +15,7 @@ menuconfig PM_DEVFREQ_EVENT
|
|||
if PM_DEVFREQ_EVENT
|
||||
|
||||
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
|
||||
select PM_OPP
|
||||
select REGMAP_MMIO
|
||||
|
@ -24,7 +24,7 @@ config DEVFREQ_EVENT_EXYNOS_NOCP
|
|||
(Network on Chip) Probe counters to measure the bandwidth of AXI bus.
|
||||
|
||||
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
|
||||
select PM_OPP
|
||||
help
|
||||
|
@ -34,7 +34,7 @@ config DEVFREQ_EVENT_EXYNOS_PPMU
|
|||
|
||||
config DEVFREQ_EVENT_ROCKCHIP_DFI
|
||||
tristate "ROCKCHIP DFI DEVFREQ event Driver"
|
||||
depends on ARCH_ROCKCHIP
|
||||
depends on ARCH_ROCKCHIP || COMPILE_TEST
|
||||
help
|
||||
This add the devfreq-event driver for Rockchip SoC. It provides DFI
|
||||
(DDR Monitor Module) driver to count ddr load.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// 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.
|
||||
* Author : Chanwoo Choi <cw00.choi@samsung.com>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* 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.
|
||||
* Author : Chanwoo Choi <cw00.choi@samsung.com>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// 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.
|
||||
* Author : Chanwoo Choi <cw00.choi@samsung.com>
|
||||
|
@ -101,17 +101,22 @@ static struct __exynos_ppmu_events {
|
|||
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;
|
||||
|
||||
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 -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
|
||||
*/
|
||||
|
@ -556,13 +561,11 @@ static int of_get_devfreq_events(struct device_node *np,
|
|||
* use default if not.
|
||||
*/
|
||||
if (info->ppmu_type == EXYNOS_TYPE_PPMU_V2) {
|
||||
struct devfreq_event_dev edev;
|
||||
int id;
|
||||
/* Not all registers take the same value for
|
||||
* read+write data count.
|
||||
*/
|
||||
edev.desc = &desc[j];
|
||||
id = exynos_ppmu_find_ppmu_id(&edev);
|
||||
id = __exynos_ppmu_find_ppmu_id(desc[j].name);
|
||||
|
||||
switch (id) {
|
||||
case PPMU_PMNCNT0:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* 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.
|
||||
* Author : Chanwoo Choi <cw00.choi@samsung.com>
|
||||
|
|
|
@ -177,7 +177,6 @@ static int rockchip_dfi_probe(struct platform_device *pdev)
|
|||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct rockchip_dfi *data;
|
||||
struct resource *res;
|
||||
struct devfreq_event_desc *desc;
|
||||
struct device_node *np = pdev->dev.of_node, *node;
|
||||
|
||||
|
@ -185,8 +184,7 @@ static int rockchip_dfi_probe(struct platform_device *pdev)
|
|||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
data->regs = devm_ioremap_resource(&pdev->dev, res);
|
||||
data->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(data->regs))
|
||||
return PTR_ERR(data->regs);
|
||||
|
||||
|
@ -200,6 +198,7 @@ static int rockchip_dfi_probe(struct platform_device *pdev)
|
|||
node = of_parse_phandle(np, "rockchip,pmu", 0);
|
||||
if (node) {
|
||||
data->regmap_pmu = syscon_node_to_regmap(node);
|
||||
of_node_put(node);
|
||||
if (IS_ERR(data->regmap_pmu))
|
||||
return PTR_ERR(data->regmap_pmu);
|
||||
}
|
||||
|
|
|
@ -15,11 +15,10 @@
|
|||
#include <linux/device.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define DEFAULT_SATURATION_RATIO 40
|
||||
|
||||
|
@ -127,6 +126,7 @@ static int exynos_bus_get_dev_status(struct device *dev,
|
|||
|
||||
ret = exynos_bus_get_event(bus, &edata);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to get event from devfreq-event devices\n");
|
||||
stat->total_time = stat->busy_time = 0;
|
||||
goto err;
|
||||
}
|
||||
|
@ -287,14 +287,106 @@ static int exynos_bus_parse_of(struct device_node *np,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int exynos_bus_profile_init(struct exynos_bus *bus,
|
||||
struct devfreq_dev_profile *profile)
|
||||
{
|
||||
struct device *dev = bus->dev;
|
||||
struct devfreq_simple_ondemand_data *ondemand_data;
|
||||
int ret;
|
||||
|
||||
/* Initialize the struct profile and governor data for parent device */
|
||||
profile->polling_ms = 50;
|
||||
profile->target = exynos_bus_target;
|
||||
profile->get_dev_status = exynos_bus_get_dev_status;
|
||||
profile->exit = exynos_bus_exit;
|
||||
|
||||
ondemand_data = devm_kzalloc(dev, sizeof(*ondemand_data), GFP_KERNEL);
|
||||
if (!ondemand_data)
|
||||
return -ENOMEM;
|
||||
|
||||
ondemand_data->upthreshold = 40;
|
||||
ondemand_data->downdifferential = 5;
|
||||
|
||||
/* Add devfreq device to monitor and handle the exynos bus */
|
||||
bus->devfreq = devm_devfreq_add_device(dev, profile,
|
||||
DEVFREQ_GOV_SIMPLE_ONDEMAND,
|
||||
ondemand_data);
|
||||
if (IS_ERR(bus->devfreq)) {
|
||||
dev_err(dev, "failed to add devfreq device\n");
|
||||
return PTR_ERR(bus->devfreq);
|
||||
}
|
||||
|
||||
/* Register opp_notifier to catch the change of OPP */
|
||||
ret = devm_devfreq_register_opp_notifier(dev, bus->devfreq);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to register opp notifier\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable devfreq-event to get raw data which is used to determine
|
||||
* current bus load.
|
||||
*/
|
||||
ret = exynos_bus_enable_edev(bus);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to enable devfreq-event devices\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = exynos_bus_set_event(bus);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to set event to devfreq-event devices\n");
|
||||
goto err_edev;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
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 */
|
||||
profile->target = exynos_bus_target;
|
||||
profile->exit = exynos_bus_passive_exit;
|
||||
|
||||
/* Get the instance of parent devfreq device */
|
||||
parent_devfreq = devfreq_get_devfreq_by_phandle(dev, 0);
|
||||
if (IS_ERR(parent_devfreq))
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
passive_data = devm_kzalloc(dev, sizeof(*passive_data), GFP_KERNEL);
|
||||
if (!passive_data)
|
||||
return -ENOMEM;
|
||||
|
||||
passive_data->parent = parent_devfreq;
|
||||
|
||||
/* Add devfreq device for exynos bus with passive governor */
|
||||
bus->devfreq = devm_devfreq_add_device(dev, profile, DEVFREQ_GOV_PASSIVE,
|
||||
passive_data);
|
||||
if (IS_ERR(bus->devfreq)) {
|
||||
dev_err(dev,
|
||||
"failed to add devfreq dev with passive governor\n");
|
||||
return PTR_ERR(bus->devfreq);
|
||||
}
|
||||
|
||||
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 devfreq_simple_ondemand_data *ondemand_data;
|
||||
struct devfreq_passive_data *passive_data;
|
||||
struct devfreq *parent_devfreq;
|
||||
struct exynos_bus *bus;
|
||||
int ret, max_state;
|
||||
unsigned long min_freq, max_freq;
|
||||
|
@ -332,86 +424,13 @@ static int exynos_bus_probe(struct platform_device *pdev)
|
|||
goto err_reg;
|
||||
|
||||
if (passive)
|
||||
goto passive;
|
||||
ret = exynos_bus_profile_init_passive(bus, profile);
|
||||
else
|
||||
ret = exynos_bus_profile_init(bus, profile);
|
||||
|
||||
/* Initialize the struct profile and governor data for parent device */
|
||||
profile->polling_ms = 50;
|
||||
profile->target = exynos_bus_target;
|
||||
profile->get_dev_status = exynos_bus_get_dev_status;
|
||||
profile->exit = exynos_bus_exit;
|
||||
|
||||
ondemand_data = devm_kzalloc(dev, sizeof(*ondemand_data), GFP_KERNEL);
|
||||
if (!ondemand_data) {
|
||||
ret = -ENOMEM;
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
}
|
||||
ondemand_data->upthreshold = 40;
|
||||
ondemand_data->downdifferential = 5;
|
||||
|
||||
/* Add devfreq device to monitor and handle the exynos bus */
|
||||
bus->devfreq = devm_devfreq_add_device(dev, profile,
|
||||
DEVFREQ_GOV_SIMPLE_ONDEMAND,
|
||||
ondemand_data);
|
||||
if (IS_ERR(bus->devfreq)) {
|
||||
dev_err(dev, "failed to add devfreq device\n");
|
||||
ret = PTR_ERR(bus->devfreq);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Register opp_notifier to catch the change of OPP */
|
||||
ret = devm_devfreq_register_opp_notifier(dev, bus->devfreq);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to register opp notifier\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable devfreq-event to get raw data which is used to determine
|
||||
* current bus load.
|
||||
*/
|
||||
ret = exynos_bus_enable_edev(bus);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to enable devfreq-event devices\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = exynos_bus_set_event(bus);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to set event to devfreq-event devices\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
goto out;
|
||||
passive:
|
||||
/* Initialize the struct profile and governor data for passive device */
|
||||
profile->target = exynos_bus_target;
|
||||
profile->exit = exynos_bus_passive_exit;
|
||||
|
||||
/* Get the instance of parent devfreq device */
|
||||
parent_devfreq = devfreq_get_devfreq_by_phandle(dev, 0);
|
||||
if (IS_ERR(parent_devfreq)) {
|
||||
ret = -EPROBE_DEFER;
|
||||
goto err;
|
||||
}
|
||||
|
||||
passive_data = devm_kzalloc(dev, sizeof(*passive_data), GFP_KERNEL);
|
||||
if (!passive_data) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
passive_data->parent = parent_devfreq;
|
||||
|
||||
/* Add devfreq device for exynos bus with passive governor */
|
||||
bus->devfreq = devm_devfreq_add_device(dev, profile, DEVFREQ_GOV_PASSIVE,
|
||||
passive_data);
|
||||
if (IS_ERR(bus->devfreq)) {
|
||||
dev_err(dev,
|
||||
"failed to add devfreq dev with passive governor\n");
|
||||
ret = PTR_ERR(bus->devfreq);
|
||||
goto err;
|
||||
}
|
||||
|
||||
out:
|
||||
max_state = bus->devfreq->profile->max_state;
|
||||
min_freq = (bus->devfreq->profile->freq_table[0] / 1000);
|
||||
max_freq = (bus->devfreq->profile->freq_table[max_state - 1] / 1000);
|
||||
|
|
471
drivers/devfreq/imx8m-ddrc.c
Normal file
471
drivers/devfreq/imx8m-ddrc.c
Normal file
|
@ -0,0 +1,471 @@
|
|||
// 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)
|
|||
if (res.a0) {
|
||||
dev_err(dev, "Failed to set dram param: %ld\n",
|
||||
res.a0);
|
||||
return -EINVAL;
|
||||
ret = -EINVAL;
|
||||
goto err_edev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -372,8 +373,11 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
|
|||
node = of_parse_phandle(np, "rockchip,pmu", 0);
|
||||
if (node) {
|
||||
data->regmap_pmu = syscon_node_to_regmap(node);
|
||||
if (IS_ERR(data->regmap_pmu))
|
||||
return PTR_ERR(data->regmap_pmu);
|
||||
of_node_put(node);
|
||||
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);
|
||||
|
@ -391,7 +395,8 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
|
|||
data->odt_dis_freq = data->timing.lpddr4_odt_dis_freq;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
ret = -EINVAL;
|
||||
goto err_edev;
|
||||
};
|
||||
|
||||
arm_smccc_smc(ROCKCHIP_SIP_DRAM_FREQ, 0, 0,
|
||||
|
@ -425,7 +430,8 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
|
|||
*/
|
||||
if (dev_pm_opp_of_add_table(dev)) {
|
||||
dev_err(dev, "Invalid operating-points in device tree.\n");
|
||||
return -EINVAL;
|
||||
ret = -EINVAL;
|
||||
goto err_edev;
|
||||
}
|
||||
|
||||
of_property_read_u32(np, "upthreshold",
|
||||
|
@ -465,6 +471,9 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
|
|||
|
||||
err_free_opp:
|
||||
dev_pm_opp_of_remove_table(&pdev->dev);
|
||||
err_edev:
|
||||
devfreq_event_disable_edev(data->edev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -107,6 +107,20 @@ struct devfreq_dev_profile {
|
|||
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
|
||||
* @node: list node - contains the devices with devfreq that have been
|
||||
|
@ -122,6 +136,7 @@ struct devfreq_dev_profile {
|
|||
* devfreq.nb to the corresponding register notifier call chain.
|
||||
* @work: delayed work for load monitoring.
|
||||
* @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
|
||||
* touch this.
|
||||
* @user_min_freq_req: PM QoS minimum frequency request from user (via sysfs)
|
||||
|
@ -132,15 +147,12 @@ struct devfreq_dev_profile {
|
|||
* @suspend_freq: frequency of a device set during suspend phase.
|
||||
* @resume_freq: frequency of a device set in resume phase.
|
||||
* @suspend_count: suspend requests counter for a device.
|
||||
* @total_trans: Number of devfreq transitions
|
||||
* @trans_table: Statistics of devfreq transitions
|
||||
* @time_in_state: Statistics of devfreq states
|
||||
* @last_stat_updated: The last time stat updated
|
||||
* @stats: Statistics of devfreq device behavior
|
||||
* @transition_notifier_list: list head of DEVFREQ_TRANSITION_NOTIFIER notifier
|
||||
* @nb_min: Notifier block for DEV_PM_QOS_MIN_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
|
||||
* functions except for the context of callbacks defined in struct
|
||||
|
@ -174,11 +186,8 @@ struct devfreq {
|
|||
unsigned long resume_freq;
|
||||
atomic_t suspend_count;
|
||||
|
||||
/* information for device frequency transition */
|
||||
unsigned int total_trans;
|
||||
unsigned int *trans_table;
|
||||
unsigned long *time_in_state;
|
||||
unsigned long last_stat_updated;
|
||||
/* information for device frequency transitions */
|
||||
struct devfreq_stats stats;
|
||||
|
||||
struct srcu_notifier_head transition_notifier_list;
|
||||
|
||||
|
|
Loading…
Reference in a new issue