Verified Commit da91e6f2 authored by Leonard Crestez's avatar Leonard Crestez Committed by Sebastian Krzyszkowiak

PM / devfreq: imx8m-ddrc: Measure bandwidth with perf

The imx8m ddrc has a performance monitoring block attached which can
be used to measure bandwidth usage and automatically adjust frequency.

There is already a perf driver for the ddr pmu so instead of
implementing a devfreq-events driver use the in-kernel perf API to
implement get_dev_status directly.
Signed-off-by: default avatarLeonard Crestez <leonard.crestez@nxp.com>
parent db583a0d
......@@ -13,6 +13,9 @@
#include <linux/clk-provider.h>
#include <linux/arm-smccc.h>
#include <asm/perf_event.h>
#include <linux/perf_event.h>
#define IMX_SIP_DDR_DVFS 0xc2000004
/* Query available frequencies. */
......@@ -34,6 +37,9 @@ struct imx8m_ddrc_freq {
/* Hardware limitation */
#define IMX8M_DDRC_MAX_FREQ_COUNT 4
/* Percentage ratio between PMU events and DDRC frequency */
#define IMX8M_DDRC_SATURATION_RATIO 20
/*
* i.MX8M DRAM Controller clocks have the following structure (abridged):
*
......@@ -74,6 +80,17 @@ struct imx8m_ddrc {
int freq_count;
struct imx8m_ddrc_freq freq_table[IMX8M_DDRC_MAX_FREQ_COUNT];
/* For measuring load with perf events: */
struct pmu *pmu;
struct perf_event_attr rd_event_attr;
struct perf_event_attr wr_event_attr;
struct perf_event *rd_event;
struct perf_event *wr_event;
u64 last_rd_val, last_rd_ena, last_rd_run;
u64 last_wr_val, last_wr_ena, last_wr_run;
};
static struct imx8m_ddrc_freq *imx8m_ddrc_find_freq(struct imx8m_ddrc *priv,
......@@ -285,13 +302,106 @@ static int imx8m_ddrc_get_dev_status(struct device *dev,
{
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);
if (priv->rd_event && priv->wr_event) {
u64 rd_delta, rd_val, rd_ena, rd_run;
u64 wr_delta, wr_val, wr_ena, wr_run;
rd_val = perf_event_read_value(priv->rd_event,
&rd_ena, &rd_run);
wr_val = perf_event_read_value(priv->wr_event,
&wr_ena, &wr_run);
rd_delta = (rd_val - priv->last_rd_val) *
(rd_ena - priv->last_rd_ena);
do_div(rd_delta, rd_run - priv->last_rd_run);
priv->last_rd_val = rd_val;
priv->last_rd_ena = rd_ena;
priv->last_rd_run = rd_run;
wr_delta = (wr_val - priv->last_wr_val) *
(wr_ena - priv->last_wr_ena);
do_div(wr_delta, wr_run - priv->last_wr_run);
priv->last_wr_val = wr_val;
priv->last_wr_ena = wr_ena;
priv->last_wr_run = wr_run;
stat->busy_time = 100 * (rd_delta + wr_delta) /
IMX8M_DDRC_SATURATION_RATIO;
stat->total_time = stat->current_frequency;
} else {
stat->busy_time = 0;
stat->total_time = 0;
}
return 0;
}
static int imx8m_ddrc_perf_disable(struct imx8m_ddrc *priv)
{
/* release and set to NULL */
if (!IS_ERR_OR_NULL(priv->rd_event))
perf_event_release_kernel(priv->rd_event);
if (!IS_ERR_OR_NULL(priv->wr_event))
perf_event_release_kernel(priv->wr_event);
priv->rd_event = NULL;
priv->wr_event = NULL;
return 0;
}
static int imx8m_ddrc_perf_enable(struct imx8m_ddrc *priv)
{
int ret;
priv->rd_event_attr.size = sizeof(priv->rd_event_attr);
priv->rd_event_attr.type = priv->pmu->type;
// perf_dfi_rd_data_cycles in reference manual
priv->rd_event_attr.config = 0x2a;
priv->rd_event = perf_event_create_kernel_counter(
&priv->rd_event_attr, 0, NULL, NULL, NULL);
if (IS_ERR(priv->rd_event)) {
ret = PTR_ERR(priv->rd_event);
goto err;
}
priv->wr_event_attr.size = sizeof(priv->wr_event_attr);
priv->wr_event_attr.type = priv->pmu->type;
// perf_dfi_wr_data_cycles in reference manual
priv->wr_event_attr.config = 0x2b;
priv->wr_event = perf_event_create_kernel_counter(
&priv->wr_event_attr, 0, NULL, NULL, NULL);
if (IS_ERR(priv->wr_event)) {
ret = PTR_ERR(priv->wr_event);
goto err;
}
return 0;
err:
imx8m_ddrc_perf_disable(priv);
return ret;
}
static int imx8m_ddrc_init_events(struct device *dev,
struct device_node *events_node)
{
struct imx8m_ddrc *priv = dev_get_drvdata(dev);
priv->pmu = perf_get_pmu_by_node(events_node);
if (!priv->pmu) {
dev_dbg(dev, "missing pmu for %pOF, defer!\n", events_node);
return -EPROBE_DEFER;
}
dev_info(dev, "measure bandwidth with pmu %s\n", priv->pmu->name);
return imx8m_ddrc_perf_enable(priv);
}
static int imx8m_ddrc_init_freq_info(struct device *dev)
{
struct imx8m_ddrc *priv = dev_get_drvdata(dev);
......@@ -372,6 +482,13 @@ static int imx8m_ddrc_check_opps(struct device *dev)
static void imx8m_ddrc_exit(struct device *dev)
{
struct imx8m_ddrc *priv = dev_get_drvdata(dev);
imx8m_ddrc_perf_disable(priv);
if (priv->pmu)
put_device(priv->pmu->dev);
priv->pmu = NULL;
dev_pm_opp_of_remove_table(dev);
}
......@@ -379,6 +496,7 @@ static int imx8m_ddrc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct imx8m_ddrc *priv;
struct device_node *events_node;
const char *gov = DEVFREQ_GOV_USERSPACE;
int ret;
......@@ -394,6 +512,15 @@ static int imx8m_ddrc_probe(struct platform_device *pdev)
return ret;
}
events_node = of_parse_phandle(dev->of_node, "fsl,ddr-pmu", 0);
if (events_node && of_device_is_available(events_node)) {
ret = imx8m_ddrc_init_events(dev, events_node);
of_node_put(events_node);
if (ret)
goto err;
gov = DEVFREQ_GOV_SIMPLE_ONDEMAND;
}
priv->dram_core = devm_clk_get(dev, "core");
if (IS_ERR(priv->dram_core)) {
ret = PTR_ERR(priv->dram_core);
......@@ -447,6 +574,9 @@ static int imx8m_ddrc_probe(struct platform_device *pdev)
return 0;
err:
imx8m_ddrc_perf_disable(priv);
if (priv->pmu)
put_device(priv->pmu->dev);
dev_pm_opp_of_remove_table(dev);
return ret;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment