1
0
Fork 0

Merge branch 'pm-devfreq'

* pm-devfreq: (28 commits)
  PM / devfreq: passive: fix compiler warning
  PM / devfreq: passive: Use non-devm notifiers
  PM / devfreq: exynos-bus: Convert to use dev_pm_opp_set_rate()
  PM / devfreq: exynos-bus: Correct clock enable sequence
  PM / devfreq: Correct devm_devfreq_remove_device() documentation
  PM / devfreq: events: extend events by type of counted data
  PM / devfreq: exynos-events: change matching code during probe
  PM / devfreq: tegra20: add COMMON_CLK dependency
  PM / devfreq: events: add Exynos PPMU new events
  PM / devfreq: Fix kernel oops on governor module load
  PM / devfreq: rk3399_dmc: Fix spelling typo
  PM / devfreq: Fix spelling typo
  PM / devfreq: Introduce driver for NVIDIA Tegra20
  PM / devfreq: tegra: Rename tegra-devfreq.c to tegra30-devfreq.c
  PM / devfreq: tegra: Enable COMPILE_TEST for the driver
  PM / devfreq: tegra: Support Tegra30
  PM / devfreq: tegra: Reconfigure hardware on governor's restart
  PM / devfreq: tegra: Move governor registration to driver's probe
  PM / devfreq: tegra: Mark ACTMON's governor as immutable
  PM / devfreq: tegra: Avoid inconsistency of current frequency value
  ...
alistair/sunxi64-5.4-dsi
Rafael J. Wysocki 2019-09-17 09:45:44 +02:00
commit 031f469ecf
10 changed files with 500 additions and 333 deletions

View File

@ -93,15 +93,28 @@ config ARM_EXYNOS_BUS_DEVFREQ
This does not yet operate with optimal voltages.
config ARM_TEGRA_DEVFREQ
tristate "Tegra DEVFREQ Driver"
depends on ARCH_TEGRA_124_SOC
select DEVFREQ_GOV_SIMPLE_ONDEMAND
tristate "NVIDIA Tegra30/114/124/210 DEVFREQ Driver"
depends on ARCH_TEGRA_3x_SOC || ARCH_TEGRA_114_SOC || \
ARCH_TEGRA_132_SOC || ARCH_TEGRA_124_SOC || \
ARCH_TEGRA_210_SOC || \
COMPILE_TEST
select PM_OPP
help
This adds the DEVFREQ driver for the Tegra family of SoCs.
It reads ACTMON counters of memory controllers and adjusts the
operating frequencies and voltages with OPP support.
config ARM_TEGRA20_DEVFREQ
tristate "NVIDIA Tegra20 DEVFREQ Driver"
depends on (TEGRA_MC && TEGRA20_EMC) || COMPILE_TEST
depends on COMMON_CLK
select DEVFREQ_GOV_SIMPLE_ONDEMAND
select PM_OPP
help
This adds the DEVFREQ driver for the Tegra20 family of SoCs.
It reads Memory Controller counters and adjusts the operating
frequencies and voltages with OPP support.
config ARM_RK3399_DMC_DEVFREQ
tristate "ARM RK3399 DMC DEVFREQ Driver"
depends on ARCH_ROCKCHIP

View File

@ -10,7 +10,8 @@ obj-$(CONFIG_DEVFREQ_GOV_PASSIVE) += governor_passive.o
# DEVFREQ Drivers
obj-$(CONFIG_ARM_EXYNOS_BUS_DEVFREQ) += exynos-bus.o
obj-$(CONFIG_ARM_RK3399_DMC_DEVFREQ) += rk3399_dmc.o
obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra-devfreq.o
obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra30-devfreq.o
obj-$(CONFIG_ARM_TEGRA20_DEVFREQ) += tegra20-devfreq.o
# DEVFREQ Event Drivers
obj-$(CONFIG_PM_DEVFREQ_EVENT) += event/

View File

@ -254,7 +254,7 @@ static struct devfreq_governor *try_then_request_governor(const char *name)
/* Restore previous state before return */
mutex_lock(&devfreq_list_lock);
if (err)
return ERR_PTR(err);
return (err < 0) ? ERR_PTR(err) : ERR_PTR(-EINVAL);
governor = find_devfreq_governor(name);
}
@ -402,7 +402,7 @@ static void devfreq_monitor(struct work_struct *work)
* devfreq_monitor_start() - Start load monitoring of devfreq instance
* @devfreq: the devfreq instance.
*
* Helper function for starting devfreq device load monitoing. By
* Helper function for starting devfreq device load monitoring. By
* default delayed work based monitoring is supported. Function
* to be called from governor in response to DEVFREQ_GOV_START
* event when device is added to devfreq framework.
@ -420,7 +420,7 @@ EXPORT_SYMBOL(devfreq_monitor_start);
* devfreq_monitor_stop() - Stop load monitoring of a devfreq instance
* @devfreq: the devfreq instance.
*
* Helper function to stop devfreq device load monitoing. Function
* Helper function to stop devfreq device load monitoring. Function
* to be called from governor in response to DEVFREQ_GOV_STOP
* event when device is removed from devfreq framework.
*/
@ -434,7 +434,7 @@ EXPORT_SYMBOL(devfreq_monitor_stop);
* devfreq_monitor_suspend() - Suspend load monitoring of a devfreq instance
* @devfreq: the devfreq instance.
*
* Helper function to suspend devfreq device load monitoing. Function
* Helper function to suspend devfreq device load monitoring. Function
* to be called from governor in response to DEVFREQ_GOV_SUSPEND
* event or when polling interval is set to zero.
*
@ -461,7 +461,7 @@ EXPORT_SYMBOL(devfreq_monitor_suspend);
* devfreq_monitor_resume() - Resume load monitoring of a devfreq instance
* @devfreq: the devfreq instance.
*
* Helper function to resume devfreq device load monitoing. Function
* Helper function to resume devfreq device load monitoring. Function
* to be called from governor in response to DEVFREQ_GOV_RESUME
* event or when polling interval is set to non-zero.
*/
@ -867,7 +867,7 @@ EXPORT_SYMBOL_GPL(devfreq_get_devfreq_by_phandle);
/**
* devm_devfreq_remove_device() - Resource-managed devfreq_remove_device()
* @dev: the device to add devfreq feature.
* @dev: the device from which to remove devfreq feature.
* @devfreq: the devfreq instance to be removed
*/
void devm_devfreq_remove_device(struct device *dev, struct devfreq *devfreq)

View File

@ -13,6 +13,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/suspend.h>
@ -20,6 +21,11 @@
#include "exynos-ppmu.h"
enum exynos_ppmu_type {
EXYNOS_TYPE_PPMU,
EXYNOS_TYPE_PPMU_V2,
};
struct exynos_ppmu_data {
struct clk *clk;
};
@ -33,6 +39,7 @@ struct exynos_ppmu {
struct regmap *regmap;
struct exynos_ppmu_data ppmu;
enum exynos_ppmu_type ppmu_type;
};
#define PPMU_EVENT(name) \
@ -86,6 +93,12 @@ static struct __exynos_ppmu_events {
PPMU_EVENT(d1-cpu),
PPMU_EVENT(d1-general),
PPMU_EVENT(d1-rt),
/* For Exynos5422 SoC */
PPMU_EVENT(dmc0_0),
PPMU_EVENT(dmc0_1),
PPMU_EVENT(dmc1_0),
PPMU_EVENT(dmc1_1),
};
static int exynos_ppmu_find_ppmu_id(struct devfreq_event_dev *edev)
@ -151,9 +164,9 @@ static int exynos_ppmu_set_event(struct devfreq_event_dev *edev)
if (ret < 0)
return ret;
/* Set the event of Read/Write data count */
/* Set the event of proper data type monitoring */
ret = regmap_write(info->regmap, PPMU_BEVTxSEL(id),
PPMU_RO_DATA_CNT | PPMU_WO_DATA_CNT);
edev->desc->event_type);
if (ret < 0)
return ret;
@ -365,23 +378,11 @@ static int exynos_ppmu_v2_set_event(struct devfreq_event_dev *edev)
if (ret < 0)
return ret;
/* Set the event of Read/Write data count */
switch (id) {
case PPMU_PMNCNT0:
case PPMU_PMNCNT1:
case PPMU_PMNCNT2:
ret = regmap_write(info->regmap, PPMU_V2_CH_EVx_TYPE(id),
PPMU_V2_RO_DATA_CNT | PPMU_V2_WO_DATA_CNT);
if (ret < 0)
return ret;
break;
case PPMU_PMNCNT3:
ret = regmap_write(info->regmap, PPMU_V2_CH_EVx_TYPE(id),
PPMU_V2_EVT3_RW_DATA_CNT);
if (ret < 0)
return ret;
break;
}
/* Set the event of proper data type monitoring */
ret = regmap_write(info->regmap, PPMU_V2_CH_EVx_TYPE(id),
edev->desc->event_type);
if (ret < 0)
return ret;
/* Reset cycle counter/performance counter and enable PPMU */
ret = regmap_read(info->regmap, PPMU_V2_PMNC, &pmnc);
@ -480,31 +481,24 @@ static const struct devfreq_event_ops exynos_ppmu_v2_ops = {
static const struct of_device_id exynos_ppmu_id_match[] = {
{
.compatible = "samsung,exynos-ppmu",
.data = (void *)&exynos_ppmu_ops,
.data = (void *)EXYNOS_TYPE_PPMU,
}, {
.compatible = "samsung,exynos-ppmu-v2",
.data = (void *)&exynos_ppmu_v2_ops,
.data = (void *)EXYNOS_TYPE_PPMU_V2,
},
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, exynos_ppmu_id_match);
static struct devfreq_event_ops *exynos_bus_get_ops(struct device_node *np)
{
const struct of_device_id *match;
match = of_match_node(exynos_ppmu_id_match, np);
return (struct devfreq_event_ops *)match->data;
}
static int of_get_devfreq_events(struct device_node *np,
struct exynos_ppmu *info)
{
struct devfreq_event_desc *desc;
struct devfreq_event_ops *event_ops;
struct device *dev = info->dev;
struct device_node *events_np, *node;
int i, j, count;
const struct of_device_id *of_id;
int ret;
events_np = of_get_child_by_name(np, "events");
if (!events_np) {
@ -512,7 +506,6 @@ static int of_get_devfreq_events(struct device_node *np,
"failed to get child node of devfreq-event devices\n");
return -EINVAL;
}
event_ops = exynos_bus_get_ops(np);
count = of_get_child_count(events_np);
desc = devm_kcalloc(dev, count, sizeof(*desc), GFP_KERNEL);
@ -520,6 +513,12 @@ static int of_get_devfreq_events(struct device_node *np,
return -ENOMEM;
info->num_events = count;
of_id = of_match_device(exynos_ppmu_id_match, dev);
if (of_id)
info->ppmu_type = (enum exynos_ppmu_type)of_id->data;
else
return -EINVAL;
j = 0;
for_each_child_of_node(events_np, node) {
for (i = 0; i < ARRAY_SIZE(ppmu_events); i++) {
@ -537,10 +536,51 @@ static int of_get_devfreq_events(struct device_node *np,
continue;
}
desc[j].ops = event_ops;
switch (info->ppmu_type) {
case EXYNOS_TYPE_PPMU:
desc[j].ops = &exynos_ppmu_ops;
break;
case EXYNOS_TYPE_PPMU_V2:
desc[j].ops = &exynos_ppmu_v2_ops;
break;
}
desc[j].driver_data = info;
of_property_read_string(node, "event-name", &desc[j].name);
ret = of_property_read_u32(node, "event-data-type",
&desc[j].event_type);
if (ret) {
/* Set the event of proper data type counting.
* Check if the data type has been defined in DT,
* 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);
switch (id) {
case PPMU_PMNCNT0:
case PPMU_PMNCNT1:
case PPMU_PMNCNT2:
desc[j].event_type = PPMU_V2_RO_DATA_CNT
| PPMU_V2_WO_DATA_CNT;
break;
case PPMU_PMNCNT3:
desc[j].event_type =
PPMU_V2_EVT3_RW_DATA_CNT;
break;
}
} else {
desc[j].event_type = PPMU_RO_DATA_CNT |
PPMU_WO_DATA_CNT;
}
}
j++;
}

View File

@ -22,7 +22,6 @@
#include <linux/slab.h>
#define DEFAULT_SATURATION_RATIO 40
#define DEFAULT_VOLTAGE_TOLERANCE 2
struct exynos_bus {
struct device *dev;
@ -34,9 +33,8 @@ struct exynos_bus {
unsigned long curr_freq;
struct regulator *regulator;
struct opp_table *opp_table;
struct clk *clk;
unsigned int voltage_tolerance;
unsigned int ratio;
};
@ -90,62 +88,29 @@ static int exynos_bus_get_event(struct exynos_bus *bus,
}
/*
* Must necessary function for devfreq simple-ondemand governor
* devfreq function for both simple-ondemand and passive governor
*/
static int exynos_bus_target(struct device *dev, unsigned long *freq, u32 flags)
{
struct exynos_bus *bus = dev_get_drvdata(dev);
struct dev_pm_opp *new_opp;
unsigned long old_freq, new_freq, new_volt, tol;
int ret = 0;
/* Get new opp-bus instance according to new bus clock */
/* Get correct frequency for bus. */
new_opp = devfreq_recommended_opp(dev, freq, flags);
if (IS_ERR(new_opp)) {
dev_err(dev, "failed to get recommended opp instance\n");
return PTR_ERR(new_opp);
}
new_freq = dev_pm_opp_get_freq(new_opp);
new_volt = dev_pm_opp_get_voltage(new_opp);
dev_pm_opp_put(new_opp);
old_freq = bus->curr_freq;
if (old_freq == new_freq)
return 0;
tol = new_volt * bus->voltage_tolerance / 100;
/* Change voltage and frequency according to new OPP level */
mutex_lock(&bus->lock);
ret = dev_pm_opp_set_rate(dev, *freq);
if (!ret)
bus->curr_freq = *freq;
if (old_freq < new_freq) {
ret = regulator_set_voltage_tol(bus->regulator, new_volt, tol);
if (ret < 0) {
dev_err(bus->dev, "failed to set voltage\n");
goto out;
}
}
ret = clk_set_rate(bus->clk, new_freq);
if (ret < 0) {
dev_err(dev, "failed to change clock of bus\n");
clk_set_rate(bus->clk, old_freq);
goto out;
}
if (old_freq > new_freq) {
ret = regulator_set_voltage_tol(bus->regulator, new_volt, tol);
if (ret < 0) {
dev_err(bus->dev, "failed to set voltage\n");
goto out;
}
}
bus->curr_freq = new_freq;
dev_dbg(dev, "Set the frequency of bus (%luHz -> %luHz, %luHz)\n",
old_freq, new_freq, clk_get_rate(bus->clk));
out:
mutex_unlock(&bus->lock);
return ret;
@ -191,57 +156,12 @@ static void exynos_bus_exit(struct device *dev)
if (ret < 0)
dev_warn(dev, "failed to disable the devfreq-event devices\n");
if (bus->regulator)
regulator_disable(bus->regulator);
dev_pm_opp_of_remove_table(dev);
clk_disable_unprepare(bus->clk);
}
/*
* Must necessary function for devfreq passive governor
*/
static int exynos_bus_passive_target(struct device *dev, unsigned long *freq,
u32 flags)
{
struct exynos_bus *bus = dev_get_drvdata(dev);
struct dev_pm_opp *new_opp;
unsigned long old_freq, new_freq;
int ret = 0;
/* Get new opp-bus instance according to new bus clock */
new_opp = devfreq_recommended_opp(dev, freq, flags);
if (IS_ERR(new_opp)) {
dev_err(dev, "failed to get recommended opp instance\n");
return PTR_ERR(new_opp);
if (bus->opp_table) {
dev_pm_opp_put_regulators(bus->opp_table);
bus->opp_table = NULL;
}
new_freq = dev_pm_opp_get_freq(new_opp);
dev_pm_opp_put(new_opp);
old_freq = bus->curr_freq;
if (old_freq == new_freq)
return 0;
/* Change the frequency according to new OPP level */
mutex_lock(&bus->lock);
ret = clk_set_rate(bus->clk, new_freq);
if (ret < 0) {
dev_err(dev, "failed to set the clock of bus\n");
goto out;
}
*freq = new_freq;
bus->curr_freq = new_freq;
dev_dbg(dev, "Set the frequency of bus (%luHz -> %luHz, %luHz)\n",
old_freq, new_freq, clk_get_rate(bus->clk));
out:
mutex_unlock(&bus->lock);
return ret;
}
static void exynos_bus_passive_exit(struct device *dev)
@ -256,21 +176,19 @@ static int exynos_bus_parent_parse_of(struct device_node *np,
struct exynos_bus *bus)
{
struct device *dev = bus->dev;
struct opp_table *opp_table;
const char *vdd = "vdd";
int i, ret, count, size;
/* Get the regulator to provide each bus with the power */
bus->regulator = devm_regulator_get(dev, "vdd");
if (IS_ERR(bus->regulator)) {
dev_err(dev, "failed to get VDD regulator\n");
return PTR_ERR(bus->regulator);
}
ret = regulator_enable(bus->regulator);
if (ret < 0) {
dev_err(dev, "failed to enable VDD regulator\n");
opp_table = dev_pm_opp_set_regulators(dev, &vdd, 1);
if (IS_ERR(opp_table)) {
ret = PTR_ERR(opp_table);
dev_err(dev, "failed to set regulators %d\n", ret);
return ret;
}
bus->opp_table = opp_table;
/*
* Get the devfreq-event devices to get the current utilization of
* buses. This raw data will be used in devfreq ondemand governor.
@ -311,14 +229,11 @@ static int exynos_bus_parent_parse_of(struct device_node *np,
if (of_property_read_u32(np, "exynos,saturation-ratio", &bus->ratio))
bus->ratio = DEFAULT_SATURATION_RATIO;
if (of_property_read_u32(np, "exynos,voltage-tolerance",
&bus->voltage_tolerance))
bus->voltage_tolerance = DEFAULT_VOLTAGE_TOLERANCE;
return 0;
err_regulator:
regulator_disable(bus->regulator);
dev_pm_opp_put_regulators(bus->opp_table);
bus->opp_table = NULL;
return ret;
}
@ -383,6 +298,7 @@ static int exynos_bus_probe(struct platform_device *pdev)
struct exynos_bus *bus;
int ret, max_state;
unsigned long min_freq, max_freq;
bool passive = false;
if (!np) {
dev_err(dev, "failed to find devicetree node\n");
@ -396,27 +312,27 @@ static int exynos_bus_probe(struct platform_device *pdev)
bus->dev = &pdev->dev;
platform_set_drvdata(pdev, bus);
/* Parse the device-tree to get the resource information */
ret = exynos_bus_parse_of(np, bus);
if (ret < 0)
return ret;
profile = devm_kzalloc(dev, sizeof(*profile), GFP_KERNEL);
if (!profile) {
ret = -ENOMEM;
goto err;
}
if (!profile)
return -ENOMEM;
node = of_parse_phandle(dev->of_node, "devfreq", 0);
if (node) {
of_node_put(node);
goto passive;
passive = true;
} else {
ret = exynos_bus_parent_parse_of(np, bus);
if (ret < 0)
return ret;
}
/* Parse the device-tree to get the resource information */
ret = exynos_bus_parse_of(np, bus);
if (ret < 0)
goto err;
goto err_reg;
if (passive)
goto passive;
/* Initialize the struct profile and governor data for parent device */
profile->polling_ms = 50;
@ -468,7 +384,7 @@ static int exynos_bus_probe(struct platform_device *pdev)
goto out;
passive:
/* Initialize the struct profile and governor data for passive device */
profile->target = exynos_bus_passive_target;
profile->target = exynos_bus_target;
profile->exit = exynos_bus_passive_exit;
/* Get the instance of parent devfreq device */
@ -507,6 +423,11 @@ out:
err:
dev_pm_opp_of_remove_table(dev);
clk_disable_unprepare(bus->clk);
err_reg:
if (!passive) {
dev_pm_opp_put_regulators(bus->opp_table);
bus->opp_table = NULL;
}
return ret;
}

View File

@ -149,7 +149,6 @@ static int devfreq_passive_notifier_call(struct notifier_block *nb,
static int devfreq_passive_event_handler(struct devfreq *devfreq,
unsigned int event, void *data)
{
struct device *dev = devfreq->dev.parent;
struct devfreq_passive_data *p_data
= (struct devfreq_passive_data *)devfreq->data;
struct devfreq *parent = (struct devfreq *)p_data->parent;
@ -165,12 +164,12 @@ static int devfreq_passive_event_handler(struct devfreq *devfreq,
p_data->this = devfreq;
nb->notifier_call = devfreq_passive_notifier_call;
ret = devm_devfreq_register_notifier(dev, parent, nb,
ret = devfreq_register_notifier(parent, nb,
DEVFREQ_TRANSITION_NOTIFIER);
break;
case DEVFREQ_GOV_STOP:
devm_devfreq_unregister_notifier(dev, parent, nb,
DEVFREQ_TRANSITION_NOTIFIER);
WARN_ON(devfreq_unregister_notifier(parent, nb,
DEVFREQ_TRANSITION_NOTIFIER));
break;
default:
break;

View File

@ -351,7 +351,7 @@ static int rk3399_dmcfreq_probe(struct platform_device *pdev)
/*
* Get dram timing and pass it to arm trust firmware,
* the dram drvier in arm trust firmware will get these
* the dram driver in arm trust firmware will get these
* timing and to do dram initial.
*/
if (!of_get_ddr_timings(&data->timing, np)) {

View File

@ -0,0 +1,212 @@
// SPDX-License-Identifier: GPL-2.0
/*
* NVIDIA Tegra20 devfreq driver
*
* Copyright (C) 2019 GRATE-DRIVER project
*/
#include <linux/clk.h>
#include <linux/devfreq.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <linux/slab.h>
#include <soc/tegra/mc.h>
#include "governor.h"
#define MC_STAT_CONTROL 0x90
#define MC_STAT_EMC_CLOCK_LIMIT 0xa0
#define MC_STAT_EMC_CLOCKS 0xa4
#define MC_STAT_EMC_CONTROL 0xa8
#define MC_STAT_EMC_COUNT 0xb8
#define EMC_GATHER_CLEAR (1 << 8)
#define EMC_GATHER_ENABLE (3 << 8)
struct tegra_devfreq {
struct devfreq *devfreq;
struct clk *emc_clock;
void __iomem *regs;
};
static int tegra_devfreq_target(struct device *dev, unsigned long *freq,
u32 flags)
{
struct tegra_devfreq *tegra = dev_get_drvdata(dev);
struct devfreq *devfreq = tegra->devfreq;
struct dev_pm_opp *opp;
unsigned long rate;
int err;
opp = devfreq_recommended_opp(dev, freq, flags);
if (IS_ERR(opp))
return PTR_ERR(opp);
rate = dev_pm_opp_get_freq(opp);
dev_pm_opp_put(opp);
err = clk_set_min_rate(tegra->emc_clock, rate);
if (err)
return err;
err = clk_set_rate(tegra->emc_clock, 0);
if (err)
goto restore_min_rate;
return 0;
restore_min_rate:
clk_set_min_rate(tegra->emc_clock, devfreq->previous_freq);
return err;
}
static int tegra_devfreq_get_dev_status(struct device *dev,
struct devfreq_dev_status *stat)
{
struct tegra_devfreq *tegra = dev_get_drvdata(dev);
/*
* EMC_COUNT returns number of memory events, that number is lower
* than the number of clocks. Conversion ratio of 1/8 results in a
* bit higher bandwidth than actually needed, it is good enough for
* the time being because drivers don't support requesting minimum
* needed memory bandwidth yet.
*
* TODO: adjust the ratio value once relevant drivers will support
* memory bandwidth management.
*/
stat->busy_time = readl_relaxed(tegra->regs + MC_STAT_EMC_COUNT);
stat->total_time = readl_relaxed(tegra->regs + MC_STAT_EMC_CLOCKS) / 8;
stat->current_frequency = clk_get_rate(tegra->emc_clock);
writel_relaxed(EMC_GATHER_CLEAR, tegra->regs + MC_STAT_CONTROL);
writel_relaxed(EMC_GATHER_ENABLE, tegra->regs + MC_STAT_CONTROL);
return 0;
}
static struct devfreq_dev_profile tegra_devfreq_profile = {
.polling_ms = 500,
.target = tegra_devfreq_target,
.get_dev_status = tegra_devfreq_get_dev_status,
};
static struct tegra_mc *tegra_get_memory_controller(void)
{
struct platform_device *pdev;
struct device_node *np;
struct tegra_mc *mc;
np = of_find_compatible_node(NULL, NULL, "nvidia,tegra20-mc-gart");
if (!np)
return ERR_PTR(-ENOENT);
pdev = of_find_device_by_node(np);
of_node_put(np);
if (!pdev)
return ERR_PTR(-ENODEV);
mc = platform_get_drvdata(pdev);
if (!mc)
return ERR_PTR(-EPROBE_DEFER);
return mc;
}
static int tegra_devfreq_probe(struct platform_device *pdev)
{
struct tegra_devfreq *tegra;
struct tegra_mc *mc;
unsigned long max_rate;
unsigned long rate;
int err;
mc = tegra_get_memory_controller();
if (IS_ERR(mc)) {
err = PTR_ERR(mc);
dev_err(&pdev->dev, "failed to get memory controller: %d\n",
err);
return err;
}
tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
if (!tegra)
return -ENOMEM;
/* EMC is a system-critical clock that is always enabled */
tegra->emc_clock = devm_clk_get(&pdev->dev, "emc");
if (IS_ERR(tegra->emc_clock)) {
err = PTR_ERR(tegra->emc_clock);
dev_err(&pdev->dev, "failed to get emc clock: %d\n", err);
return err;
}
tegra->regs = mc->regs;
max_rate = clk_round_rate(tegra->emc_clock, ULONG_MAX);
for (rate = 0; rate <= max_rate; rate++) {
rate = clk_round_rate(tegra->emc_clock, rate);
err = dev_pm_opp_add(&pdev->dev, rate, 0);
if (err) {
dev_err(&pdev->dev, "failed to add opp: %d\n", err);
goto remove_opps;
}
}
/*
* Reset statistic gathers state, select global bandwidth for the
* statistics collection mode and set clocks counter saturation
* limit to maximum.
*/
writel_relaxed(0x00000000, tegra->regs + MC_STAT_CONTROL);
writel_relaxed(0x00000000, tegra->regs + MC_STAT_EMC_CONTROL);
writel_relaxed(0xffffffff, tegra->regs + MC_STAT_EMC_CLOCK_LIMIT);
platform_set_drvdata(pdev, tegra);
tegra->devfreq = devfreq_add_device(&pdev->dev, &tegra_devfreq_profile,
DEVFREQ_GOV_SIMPLE_ONDEMAND, NULL);
if (IS_ERR(tegra->devfreq)) {
err = PTR_ERR(tegra->devfreq);
goto remove_opps;
}
return 0;
remove_opps:
dev_pm_opp_remove_all_dynamic(&pdev->dev);
return err;
}
static int tegra_devfreq_remove(struct platform_device *pdev)
{
struct tegra_devfreq *tegra = platform_get_drvdata(pdev);
devfreq_remove_device(tegra->devfreq);
dev_pm_opp_remove_all_dynamic(&pdev->dev);
return 0;
}
static struct platform_driver tegra_devfreq_driver = {
.probe = tegra_devfreq_probe,
.remove = tegra_devfreq_remove,
.driver = {
.name = "tegra20-devfreq",
},
};
module_platform_driver(tegra_devfreq_driver);
MODULE_ALIAS("platform:tegra20-devfreq");
MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>");
MODULE_DESCRIPTION("NVIDIA Tegra20 devfreq driver");
MODULE_LICENSE("GPL v2");

View File

@ -132,7 +132,6 @@ static struct tegra_devfreq_device_config actmon_device_configs[] = {
struct tegra_devfreq_device {
const struct tegra_devfreq_device_config *config;
void __iomem *regs;
spinlock_t lock;
/* Average event count sampled in the last interrupt */
u32 avg_count;
@ -160,6 +159,8 @@ struct tegra_devfreq {
struct notifier_block rate_change_nb;
struct tegra_devfreq_device devices[ARRAY_SIZE(actmon_device_configs)];
int irq;
};
struct tegra_actmon_emc_ratio {
@ -179,23 +180,23 @@ static struct tegra_actmon_emc_ratio actmon_emc_ratios[] = {
static u32 actmon_readl(struct tegra_devfreq *tegra, u32 offset)
{
return readl(tegra->regs + offset);
return readl_relaxed(tegra->regs + offset);
}
static void actmon_writel(struct tegra_devfreq *tegra, u32 val, u32 offset)
{
writel(val, tegra->regs + offset);
writel_relaxed(val, tegra->regs + offset);
}
static u32 device_readl(struct tegra_devfreq_device *dev, u32 offset)
{
return readl(dev->regs + offset);
return readl_relaxed(dev->regs + offset);
}
static void device_writel(struct tegra_devfreq_device *dev, u32 val,
u32 offset)
{
writel(val, dev->regs + offset);
writel_relaxed(val, dev->regs + offset);
}
static unsigned long do_percent(unsigned long val, unsigned int pct)
@ -231,18 +232,14 @@ static void tegra_devfreq_update_wmark(struct tegra_devfreq *tegra,
static void actmon_write_barrier(struct tegra_devfreq *tegra)
{
/* ensure the update has reached the ACTMON */
wmb();
actmon_readl(tegra, ACTMON_GLB_STATUS);
readl(tegra->regs + ACTMON_GLB_STATUS);
}
static void actmon_isr_device(struct tegra_devfreq *tegra,
struct tegra_devfreq_device *dev)
{
unsigned long flags;
u32 intr_status, dev_ctrl;
spin_lock_irqsave(&dev->lock, flags);
dev->avg_count = device_readl(dev, ACTMON_DEV_AVG_COUNT);
tegra_devfreq_update_avg_wmark(tegra, dev);
@ -291,26 +288,6 @@ static void actmon_isr_device(struct tegra_devfreq *tegra,
device_writel(dev, ACTMON_INTR_STATUS_CLEAR, ACTMON_DEV_INTR_STATUS);
actmon_write_barrier(tegra);
spin_unlock_irqrestore(&dev->lock, flags);
}
static irqreturn_t actmon_isr(int irq, void *data)
{
struct tegra_devfreq *tegra = data;
bool handled = false;
unsigned int i;
u32 val;
val = actmon_readl(tegra, ACTMON_GLB_STATUS);
for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) {
if (val & tegra->devices[i].config->irq_mask) {
actmon_isr_device(tegra, tegra->devices + i);
handled = true;
}
}
return handled ? IRQ_WAKE_THREAD : IRQ_NONE;
}
static unsigned long actmon_cpu_to_emc_rate(struct tegra_devfreq *tegra,
@ -337,15 +314,12 @@ static void actmon_update_target(struct tegra_devfreq *tegra,
unsigned long cpu_freq = 0;
unsigned long static_cpu_emc_freq = 0;
unsigned int avg_sustain_coef;
unsigned long flags;
if (dev->config->avg_dependency_threshold) {
cpu_freq = cpufreq_get(0);
static_cpu_emc_freq = actmon_cpu_to_emc_rate(tegra, cpu_freq);
}
spin_lock_irqsave(&dev->lock, flags);
dev->target_freq = dev->avg_count / ACTMON_SAMPLING_PERIOD;
avg_sustain_coef = 100 * 100 / dev->config->boost_up_threshold;
dev->target_freq = do_percent(dev->target_freq, avg_sustain_coef);
@ -353,19 +327,31 @@ static void actmon_update_target(struct tegra_devfreq *tegra,
if (dev->avg_count >= dev->config->avg_dependency_threshold)
dev->target_freq = max(dev->target_freq, static_cpu_emc_freq);
spin_unlock_irqrestore(&dev->lock, flags);
}
static irqreturn_t actmon_thread_isr(int irq, void *data)
{
struct tegra_devfreq *tegra = data;
bool handled = false;
unsigned int i;
u32 val;
mutex_lock(&tegra->devfreq->lock);
update_devfreq(tegra->devfreq);
val = actmon_readl(tegra, ACTMON_GLB_STATUS);
for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) {
if (val & tegra->devices[i].config->irq_mask) {
actmon_isr_device(tegra, tegra->devices + i);
handled = true;
}
}
if (handled)
update_devfreq(tegra->devfreq);
mutex_unlock(&tegra->devfreq->lock);
return IRQ_HANDLED;
return handled ? IRQ_HANDLED : IRQ_NONE;
}
static int tegra_actmon_rate_notify_cb(struct notifier_block *nb,
@ -375,7 +361,6 @@ static int tegra_actmon_rate_notify_cb(struct notifier_block *nb,
struct tegra_devfreq *tegra;
struct tegra_devfreq_device *dev;
unsigned int i;
unsigned long flags;
if (action != POST_RATE_CHANGE)
return NOTIFY_OK;
@ -387,9 +372,7 @@ static int tegra_actmon_rate_notify_cb(struct notifier_block *nb,
for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) {
dev = &tegra->devices[i];
spin_lock_irqsave(&dev->lock, flags);
tegra_devfreq_update_wmark(tegra, dev);
spin_unlock_irqrestore(&dev->lock, flags);
}
actmon_write_barrier(tegra);
@ -397,48 +380,6 @@ static int tegra_actmon_rate_notify_cb(struct notifier_block *nb,
return NOTIFY_OK;
}
static void tegra_actmon_enable_interrupts(struct tegra_devfreq *tegra)
{
struct tegra_devfreq_device *dev;
u32 val;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) {
dev = &tegra->devices[i];
val = device_readl(dev, ACTMON_DEV_CTRL);
val |= ACTMON_DEV_CTRL_AVG_ABOVE_WMARK_EN;
val |= ACTMON_DEV_CTRL_AVG_BELOW_WMARK_EN;
val |= ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN;
val |= ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN;
device_writel(dev, val, ACTMON_DEV_CTRL);
}
actmon_write_barrier(tegra);
}
static void tegra_actmon_disable_interrupts(struct tegra_devfreq *tegra)
{
struct tegra_devfreq_device *dev;
u32 val;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) {
dev = &tegra->devices[i];
val = device_readl(dev, ACTMON_DEV_CTRL);
val &= ~ACTMON_DEV_CTRL_AVG_ABOVE_WMARK_EN;
val &= ~ACTMON_DEV_CTRL_AVG_BELOW_WMARK_EN;
val &= ~ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN;
val &= ~ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN;
device_writel(dev, val, ACTMON_DEV_CTRL);
}
actmon_write_barrier(tegra);
}
static void tegra_actmon_configure_device(struct tegra_devfreq *tegra,
struct tegra_devfreq_device *dev)
{
@ -462,34 +403,80 @@ static void tegra_actmon_configure_device(struct tegra_devfreq *tegra,
<< ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_NUM_SHIFT;
val |= (ACTMON_ABOVE_WMARK_WINDOW - 1)
<< ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_NUM_SHIFT;
val |= ACTMON_DEV_CTRL_AVG_ABOVE_WMARK_EN;
val |= ACTMON_DEV_CTRL_AVG_BELOW_WMARK_EN;
val |= ACTMON_DEV_CTRL_CONSECUTIVE_BELOW_WMARK_EN;
val |= ACTMON_DEV_CTRL_CONSECUTIVE_ABOVE_WMARK_EN;
val |= ACTMON_DEV_CTRL_ENB;
device_writel(dev, val, ACTMON_DEV_CTRL);
}
static void tegra_actmon_start(struct tegra_devfreq *tegra)
{
unsigned int i;
disable_irq(tegra->irq);
actmon_writel(tegra, ACTMON_SAMPLING_PERIOD - 1,
ACTMON_GLB_PERIOD_CTRL);
for (i = 0; i < ARRAY_SIZE(tegra->devices); i++)
tegra_actmon_configure_device(tegra, &tegra->devices[i]);
actmon_write_barrier(tegra);
enable_irq(tegra->irq);
}
static void tegra_actmon_stop(struct tegra_devfreq *tegra)
{
unsigned int i;
disable_irq(tegra->irq);
for (i = 0; i < ARRAY_SIZE(tegra->devices); i++) {
device_writel(&tegra->devices[i], 0x00000000, ACTMON_DEV_CTRL);
device_writel(&tegra->devices[i], ACTMON_INTR_STATUS_CLEAR,
ACTMON_DEV_INTR_STATUS);
}
actmon_write_barrier(tegra);
enable_irq(tegra->irq);
}
static int tegra_devfreq_target(struct device *dev, unsigned long *freq,
u32 flags)
{
struct tegra_devfreq *tegra = dev_get_drvdata(dev);
struct devfreq *devfreq = tegra->devfreq;
struct dev_pm_opp *opp;
unsigned long rate = *freq * KHZ;
unsigned long rate;
int err;
opp = devfreq_recommended_opp(dev, &rate, flags);
opp = devfreq_recommended_opp(dev, freq, flags);
if (IS_ERR(opp)) {
dev_err(dev, "Failed to find opp for %lu KHz\n", *freq);
dev_err(dev, "Failed to find opp for %lu Hz\n", *freq);
return PTR_ERR(opp);
}
rate = dev_pm_opp_get_freq(opp);
dev_pm_opp_put(opp);
clk_set_min_rate(tegra->emc_clock, rate);
clk_set_rate(tegra->emc_clock, 0);
err = clk_set_min_rate(tegra->emc_clock, rate);
if (err)
return err;
*freq = rate;
err = clk_set_rate(tegra->emc_clock, 0);
if (err)
goto restore_min_rate;
return 0;
restore_min_rate:
clk_set_min_rate(tegra->emc_clock, devfreq->previous_freq);
return err;
}
static int tegra_devfreq_get_dev_status(struct device *dev,
@ -497,13 +484,15 @@ static int tegra_devfreq_get_dev_status(struct device *dev,
{
struct tegra_devfreq *tegra = dev_get_drvdata(dev);
struct tegra_devfreq_device *actmon_dev;
unsigned long cur_freq;
stat->current_frequency = tegra->cur_freq;
cur_freq = READ_ONCE(tegra->cur_freq);
/* To be used by the tegra governor */
stat->private_data = tegra;
/* The below are to be used by the other governors */
stat->current_frequency = cur_freq * KHZ;
actmon_dev = &tegra->devices[MCALL];
@ -514,7 +503,7 @@ static int tegra_devfreq_get_dev_status(struct device *dev,
stat->busy_time *= 100 / BUS_SATURATION_RATIO;
/* Number of cycles in a sampling period */
stat->total_time = ACTMON_SAMPLING_PERIOD * tegra->cur_freq;
stat->total_time = ACTMON_SAMPLING_PERIOD * cur_freq;
stat->busy_time = min(stat->busy_time, stat->total_time);
@ -553,7 +542,7 @@ static int tegra_governor_get_target(struct devfreq *devfreq,
target_freq = max(target_freq, dev->target_freq);
}
*freq = target_freq;
*freq = target_freq * KHZ;
return 0;
}
@ -566,22 +555,22 @@ static int tegra_governor_event_handler(struct devfreq *devfreq,
switch (event) {
case DEVFREQ_GOV_START:
devfreq_monitor_start(devfreq);
tegra_actmon_enable_interrupts(tegra);
tegra_actmon_start(tegra);
break;
case DEVFREQ_GOV_STOP:
tegra_actmon_disable_interrupts(tegra);
tegra_actmon_stop(tegra);
devfreq_monitor_stop(devfreq);
break;
case DEVFREQ_GOV_SUSPEND:
tegra_actmon_disable_interrupts(tegra);
tegra_actmon_stop(tegra);
devfreq_monitor_suspend(devfreq);
break;
case DEVFREQ_GOV_RESUME:
devfreq_monitor_resume(devfreq);
tegra_actmon_enable_interrupts(tegra);
tegra_actmon_start(tegra);
break;
}
@ -592,25 +581,22 @@ static struct devfreq_governor tegra_devfreq_governor = {
.name = "tegra_actmon",
.get_target_freq = tegra_governor_get_target,
.event_handler = tegra_governor_event_handler,
.immutable = true,
};
static int tegra_devfreq_probe(struct platform_device *pdev)
{
struct tegra_devfreq *tegra;
struct tegra_devfreq_device *dev;
struct resource *res;
unsigned int i;
unsigned long rate;
int irq;
int err;
tegra = devm_kzalloc(&pdev->dev, sizeof(*tegra), GFP_KERNEL);
if (!tegra)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
tegra->regs = devm_ioremap_resource(&pdev->dev, res);
tegra->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(tegra->regs))
return PTR_ERR(tegra->regs);
@ -632,13 +618,10 @@ static int tegra_devfreq_probe(struct platform_device *pdev)
return PTR_ERR(tegra->emc_clock);
}
clk_set_rate(tegra->emc_clock, ULONG_MAX);
tegra->rate_change_nb.notifier_call = tegra_actmon_rate_notify_cb;
err = clk_notifier_register(tegra->emc_clock, &tegra->rate_change_nb);
if (err) {
dev_err(&pdev->dev,
"Failed to register rate change notifier\n");
tegra->irq = platform_get_irq(pdev, 0);
if (tegra->irq < 0) {
err = tegra->irq;
dev_err(&pdev->dev, "Failed to get IRQ: %d\n", err);
return err;
}
@ -656,73 +639,94 @@ static int tegra_devfreq_probe(struct platform_device *pdev)
tegra->max_freq = clk_round_rate(tegra->emc_clock, ULONG_MAX) / KHZ;
tegra->cur_freq = clk_get_rate(tegra->emc_clock) / KHZ;
actmon_writel(tegra, ACTMON_SAMPLING_PERIOD - 1,
ACTMON_GLB_PERIOD_CTRL);
for (i = 0; i < ARRAY_SIZE(actmon_device_configs); i++) {
dev = tegra->devices + i;
dev->config = actmon_device_configs + i;
dev->regs = tegra->regs + dev->config->offset;
spin_lock_init(&dev->lock);
tegra_actmon_configure_device(tegra, dev);
}
for (rate = 0; rate <= tegra->max_freq * KHZ; rate++) {
rate = clk_round_rate(tegra->emc_clock, rate);
dev_pm_opp_add(&pdev->dev, rate, 0);
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "Failed to get IRQ: %d\n", irq);
return irq;
err = dev_pm_opp_add(&pdev->dev, rate, 0);
if (err) {
dev_err(&pdev->dev, "Failed to add OPP: %d\n", err);
goto remove_opps;
}
}
platform_set_drvdata(pdev, tegra);
err = devm_request_threaded_irq(&pdev->dev, irq, actmon_isr,
actmon_thread_isr, IRQF_SHARED,
"tegra-devfreq", tegra);
tegra->rate_change_nb.notifier_call = tegra_actmon_rate_notify_cb;
err = clk_notifier_register(tegra->emc_clock, &tegra->rate_change_nb);
if (err) {
dev_err(&pdev->dev, "Interrupt request failed\n");
return err;
dev_err(&pdev->dev,
"Failed to register rate change notifier\n");
goto remove_opps;
}
err = devfreq_add_governor(&tegra_devfreq_governor);
if (err) {
dev_err(&pdev->dev, "Failed to add governor: %d\n", err);
goto unreg_notifier;
}
tegra_devfreq_profile.initial_freq = clk_get_rate(tegra->emc_clock);
tegra->devfreq = devm_devfreq_add_device(&pdev->dev,
&tegra_devfreq_profile,
"tegra_actmon",
NULL);
tegra->devfreq = devfreq_add_device(&pdev->dev,
&tegra_devfreq_profile,
"tegra_actmon",
NULL);
if (IS_ERR(tegra->devfreq)) {
err = PTR_ERR(tegra->devfreq);
goto remove_governor;
}
err = devm_request_threaded_irq(&pdev->dev, tegra->irq, NULL,
actmon_thread_isr, IRQF_ONESHOT,
"tegra-devfreq", tegra);
if (err) {
dev_err(&pdev->dev, "Interrupt request failed: %d\n", err);
goto remove_devfreq;
}
return 0;
remove_devfreq:
devfreq_remove_device(tegra->devfreq);
remove_governor:
devfreq_remove_governor(&tegra_devfreq_governor);
unreg_notifier:
clk_notifier_unregister(tegra->emc_clock, &tegra->rate_change_nb);
remove_opps:
dev_pm_opp_remove_all_dynamic(&pdev->dev);
reset_control_reset(tegra->reset);
clk_disable_unprepare(tegra->clock);
return err;
}
static int tegra_devfreq_remove(struct platform_device *pdev)
{
struct tegra_devfreq *tegra = platform_get_drvdata(pdev);
int irq = platform_get_irq(pdev, 0);
u32 val;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(actmon_device_configs); i++) {
val = device_readl(&tegra->devices[i], ACTMON_DEV_CTRL);
val &= ~ACTMON_DEV_CTRL_ENB;
device_writel(&tegra->devices[i], val, ACTMON_DEV_CTRL);
}
actmon_write_barrier(tegra);
devm_free_irq(&pdev->dev, irq, tegra);
devfreq_remove_device(tegra->devfreq);
devfreq_remove_governor(&tegra_devfreq_governor);
clk_notifier_unregister(tegra->emc_clock, &tegra->rate_change_nb);
dev_pm_opp_remove_all_dynamic(&pdev->dev);
reset_control_reset(tegra->reset);
clk_disable_unprepare(tegra->clock);
return 0;
}
static const struct of_device_id tegra_devfreq_of_match[] = {
{ .compatible = "nvidia,tegra30-actmon" },
{ .compatible = "nvidia,tegra124-actmon" },
{ },
};
@ -737,36 +741,7 @@ static struct platform_driver tegra_devfreq_driver = {
.of_match_table = tegra_devfreq_of_match,
},
};
static int __init tegra_devfreq_init(void)
{
int ret = 0;
ret = devfreq_add_governor(&tegra_devfreq_governor);
if (ret) {
pr_err("%s: failed to add governor: %d\n", __func__, ret);
return ret;
}
ret = platform_driver_register(&tegra_devfreq_driver);
if (ret)
devfreq_remove_governor(&tegra_devfreq_governor);
return ret;
}
module_init(tegra_devfreq_init)
static void __exit tegra_devfreq_exit(void)
{
int ret = 0;
platform_driver_unregister(&tegra_devfreq_driver);
ret = devfreq_remove_governor(&tegra_devfreq_governor);
if (ret)
pr_err("%s: failed to remove governor: %d\n", __func__, ret);
}
module_exit(tegra_devfreq_exit)
module_platform_driver(tegra_devfreq_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Tegra devfreq driver");

View File

@ -78,14 +78,20 @@ struct devfreq_event_ops {
* struct devfreq_event_desc - the descriptor of devfreq-event device
*
* @name : the name of devfreq-event device.
* @event_type : the type of the event determined and used by driver
* @driver_data : the private data for devfreq-event driver.
* @ops : the operation to control devfreq-event device.
*
* Each devfreq-event device is described with a this structure.
* This structure contains the various data for devfreq-event device.
* The event_type describes what is going to be counted in the register.
* It might choose to count e.g. read requests, write data in bytes, etc.
* The full supported list of types is present in specyfic header in:
* include/dt-bindings/pmu/.
*/
struct devfreq_event_desc {
const char *name;
u32 event_type;
void *driver_data;
const struct devfreq_event_ops *ops;