Commit 4ce3048c authored by Dmitry Osipenko's avatar Dmitry Osipenko Committed by Thierry Reding

drm/tegra: dc: Support OPP and SoC core voltage scaling

Add OPP and SoC core voltage scaling support to the display controller
driver. This is required for enabling system-wide DVFS on pre-Tegra186
SoCs.
Reviewed-by: default avatarUlf Hansson <ulf.hansson@linaro.org>
Tested-by: Peter Geis <pgwipeout@gmail.com> # Ouya T30
Tested-by: Paul Fertser <fercerpav@gmail.com> # PAZ00 T20
Tested-by: Nicolas Chauvet <kwizart@gmail.com> # PAZ00 T20 and TK1 T124
Tested-by: Matt Merhar <mattmerhar@protonmail.com> # Ouya T30
Signed-off-by: default avatarDmitry Osipenko <digetx@gmail.com>
Signed-off-by: default avatarThierry Reding <treding@nvidia.com>
parent a21115dd
......@@ -11,9 +11,12 @@
#include <linux/interconnect.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_opp.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <soc/tegra/common.h>
#include <soc/tegra/pmc.h>
#include <drm/drm_atomic.h>
......@@ -1834,6 +1837,52 @@ int tegra_dc_state_setup_clock(struct tegra_dc *dc,
return 0;
}
static void tegra_dc_update_voltage_state(struct tegra_dc *dc,
struct tegra_dc_state *state)
{
unsigned long rate, pstate;
struct dev_pm_opp *opp;
int err;
if (!dc->has_opp_table)
return;
/* calculate actual pixel clock rate which depends on internal divider */
rate = DIV_ROUND_UP(clk_get_rate(dc->clk) * 2, state->div + 2);
/* find suitable OPP for the rate */
opp = dev_pm_opp_find_freq_ceil(dc->dev, &rate);
/*
* Very high resolution modes may results in a clock rate that is
* above the characterized maximum. In this case it's okay to fall
* back to the characterized maximum.
*/
if (opp == ERR_PTR(-ERANGE))
opp = dev_pm_opp_find_freq_floor(dc->dev, &rate);
if (IS_ERR(opp)) {
dev_err(dc->dev, "failed to find OPP for %luHz: %pe\n",
rate, opp);
return;
}
pstate = dev_pm_opp_get_required_pstate(opp, 0);
dev_pm_opp_put(opp);
/*
* The minimum core voltage depends on the pixel clock rate (which
* depends on internal clock divider of the CRTC) and not on the
* rate of the display controller clock. This is why we're not using
* dev_pm_opp_set_rate() API and instead controlling the power domain
* directly.
*/
err = dev_pm_genpd_set_performance_state(dc->dev, pstate);
if (err)
dev_err(dc->dev, "failed to set power domain state to %lu: %d\n",
pstate, err);
}
static void tegra_dc_set_clock_rate(struct tegra_dc *dc,
struct tegra_dc_state *state)
{
......@@ -1867,6 +1916,8 @@ static void tegra_dc_set_clock_rate(struct tegra_dc *dc,
DRM_DEBUG_KMS("rate: %lu, div: %u\n", clk_get_rate(dc->clk),
state->div);
DRM_DEBUG_KMS("pclk: %lu\n", state->pclk);
tegra_dc_update_voltage_state(dc, state);
}
static void tegra_dc_stop(struct tegra_dc *dc)
......@@ -2057,6 +2108,13 @@ static void tegra_crtc_atomic_disable(struct drm_crtc *crtc,
err = host1x_client_suspend(&dc->client);
if (err < 0)
dev_err(dc->dev, "failed to suspend: %d\n", err);
if (dc->has_opp_table) {
err = dev_pm_genpd_set_performance_state(dc->dev, 0);
if (err)
dev_err(dc->dev,
"failed to clear power domain state: %d\n", err);
}
}
static void tegra_crtc_atomic_enable(struct drm_crtc *crtc,
......@@ -3058,6 +3116,23 @@ static int tegra_dc_couple(struct tegra_dc *dc)
return 0;
}
static int tegra_dc_init_opp_table(struct tegra_dc *dc)
{
struct tegra_core_opp_params opp_params = {};
int err;
err = devm_tegra_core_dev_init_opp_table(dc->dev, &opp_params);
if (err && err != -ENODEV)
return err;
if (err)
dc->has_opp_table = false;
else
dc->has_opp_table = true;
return 0;
}
static int tegra_dc_probe(struct platform_device *pdev)
{
u64 dma_mask = dma_get_mask(pdev->dev.parent);
......@@ -3123,6 +3198,10 @@ static int tegra_dc_probe(struct platform_device *pdev)
tegra_powergate_power_off(dc->powergate);
}
err = tegra_dc_init_opp_table(dc);
if (err < 0)
return err;
dc->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(dc->regs))
return PTR_ERR(dc->regs);
......
......@@ -101,6 +101,8 @@ struct tegra_dc {
struct drm_info_list *debugfs_files;
const struct tegra_dc_soc_info *soc;
bool has_opp_table;
};
static inline struct tegra_dc *
......
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