mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-15 15:15:47 +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
|
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
|
||||||
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
|
||||||
|
|
|
@ -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"
|
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
|
||||||
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
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
# 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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 @@
|
||||||
#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 @@
|
||||||
#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)
|
||||||
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)
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
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,
|
||||||
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)
|
||||||
}
|
}
|
||||||
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,
|
||||||
|
|
||||||
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,
|
||||||
|
|
||||||
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,
|
||||||
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[] = {
|
||||||
};
|
};
|
||||||
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)
|
||||||
}
|
}
|
||||||
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)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
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
|
||||||
(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
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// 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>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* 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>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// 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 {
|
||||||
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,
|
||||||
* 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:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* 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)
|
||||||
{
|
{
|
||||||
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)
|
||||||
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)
|
||||||
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 @@
|
||||||
#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,
|
||||||
|
|
||||||
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,14 +287,106 @@ static int exynos_bus_parse_of(struct device_node *np,
|
||||||
return ret;
|
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)
|
static int exynos_bus_probe(struct platform_device *pdev)
|
||||||
{
|
{
|
||||||
struct device *dev = &pdev->dev;
|
struct device *dev = &pdev->dev;
|
||||||
struct device_node *np = dev->of_node, *node;
|
struct device_node *np = dev->of_node, *node;
|
||||||
struct devfreq_dev_profile *profile;
|
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;
|
struct exynos_bus *bus;
|
||||||
int ret, max_state;
|
int ret, max_state;
|
||||||
unsigned long min_freq, max_freq;
|
unsigned long min_freq, max_freq;
|
||||||
|
@ -332,86 +424,13 @@ static int exynos_bus_probe(struct platform_device *pdev)
|
||||||
goto err_reg;
|
goto err_reg;
|
||||||
|
|
||||||
if (passive)
|
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 */
|
if (ret < 0)
|
||||||
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;
|
|
||||||
goto err;
|
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;
|
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);
|
||||||
|
|
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) {
|
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)
|
||||||
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)
|
||||||
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)
|
||||||
*/
|
*/
|
||||||
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)
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
* 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 {
|
||||||
* @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 {
|
||||||
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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue