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:
Rafael J. Wysocki 2020-01-27 11:30:38 +01:00
commit c102671af0
16 changed files with 870 additions and 146 deletions

View File

@ -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

View File

@ -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>;
};

View File

@ -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"

View File

@ -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

View File

@ -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)
{

View File

@ -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.

View File

@ -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.

View File

@ -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>

View File

@ -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>

View File

@ -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:

View File

@ -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>

View File

@ -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);
}

View File

@ -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);

View 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");

View File

@ -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;
}

View File

@ -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;