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 ...
This commit is contained in:
commit
031f469ecf
|
@ -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
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
/* Set the event of proper data type monitoring */
|
||||
ret = regmap_write(info->regmap, PPMU_V2_CH_EVx_TYPE(id),
|
||||
PPMU_V2_RO_DATA_CNT | PPMU_V2_WO_DATA_CNT);
|
||||
edev->desc->event_type);
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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++;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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");
|
|
@ -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);
|
||||
|
||||
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 = 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");
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue