mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 19:29:37 +00:00
drm/tegra: dc: Implement runtime PM
Use runtime PM to clock-gate, assert reset and powergate the display controller. This ties in nicely with atomic DPMS in that a runtime PM reference is taken before a pipe is enabled and dropped after it has been shut down. To make sure this works, make sure to only ever update planes on active CRTCs, otherwise register accesses to a clock-gated and reset CRTC will hang the CPU. Signed-off-by: Thierry Reding <treding@nvidia.com>
This commit is contained in:
parent
2ccb396e9d
commit
33a8eb8d40
2 changed files with 107 additions and 71 deletions
|
@ -10,6 +10,7 @@
|
|||
#include <linux/clk.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#include <soc/tegra/pmc.h>
|
||||
|
@ -1216,6 +1217,8 @@ static void tegra_crtc_disable(struct drm_crtc *crtc)
|
|||
|
||||
tegra_dc_stats_reset(&dc->stats);
|
||||
drm_crtc_vblank_off(crtc);
|
||||
|
||||
pm_runtime_put_sync(dc->dev);
|
||||
}
|
||||
|
||||
static void tegra_crtc_enable(struct drm_crtc *crtc)
|
||||
|
@ -1225,6 +1228,48 @@ static void tegra_crtc_enable(struct drm_crtc *crtc)
|
|||
struct tegra_dc *dc = to_tegra_dc(crtc);
|
||||
u32 value;
|
||||
|
||||
pm_runtime_get_sync(dc->dev);
|
||||
|
||||
/* initialize display controller */
|
||||
if (dc->syncpt) {
|
||||
u32 syncpt = host1x_syncpt_id(dc->syncpt);
|
||||
|
||||
value = SYNCPT_CNTRL_NO_STALL;
|
||||
tegra_dc_writel(dc, value, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL);
|
||||
|
||||
value = SYNCPT_VSYNC_ENABLE | syncpt;
|
||||
tegra_dc_writel(dc, value, DC_CMD_CONT_SYNCPT_VSYNC);
|
||||
}
|
||||
|
||||
value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_TYPE);
|
||||
|
||||
value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY);
|
||||
|
||||
/* initialize timer */
|
||||
value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) |
|
||||
WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20);
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY);
|
||||
|
||||
value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(1) |
|
||||
WINDOW_B_THRESHOLD(1) | WINDOW_C_THRESHOLD(1);
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER);
|
||||
|
||||
value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE);
|
||||
|
||||
value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_MASK);
|
||||
|
||||
if (dc->soc->supports_border_color)
|
||||
tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR);
|
||||
|
||||
/* apply PLL and pixel clock changes */
|
||||
tegra_dc_commit_state(dc, state);
|
||||
|
||||
/* program display mode */
|
||||
|
@ -1685,7 +1730,6 @@ static int tegra_dc_init(struct host1x_client *client)
|
|||
struct tegra_drm *tegra = drm->dev_private;
|
||||
struct drm_plane *primary = NULL;
|
||||
struct drm_plane *cursor = NULL;
|
||||
u32 value;
|
||||
int err;
|
||||
|
||||
dc->syncpt = host1x_syncpt_request(dc->dev, flags);
|
||||
|
@ -1755,47 +1799,6 @@ static int tegra_dc_init(struct host1x_client *client)
|
|||
goto cleanup;
|
||||
}
|
||||
|
||||
/* initialize display controller */
|
||||
if (dc->syncpt) {
|
||||
u32 syncpt = host1x_syncpt_id(dc->syncpt);
|
||||
|
||||
value = SYNCPT_CNTRL_NO_STALL;
|
||||
tegra_dc_writel(dc, value, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL);
|
||||
|
||||
value = SYNCPT_VSYNC_ENABLE | syncpt;
|
||||
tegra_dc_writel(dc, value, DC_CMD_CONT_SYNCPT_VSYNC);
|
||||
}
|
||||
|
||||
value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_TYPE);
|
||||
|
||||
value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY);
|
||||
|
||||
/* initialize timer */
|
||||
value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) |
|
||||
WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20);
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY);
|
||||
|
||||
value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(1) |
|
||||
WINDOW_B_THRESHOLD(1) | WINDOW_C_THRESHOLD(1);
|
||||
tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER);
|
||||
|
||||
value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE);
|
||||
|
||||
value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
|
||||
WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
|
||||
tegra_dc_writel(dc, value, DC_CMD_INT_MASK);
|
||||
|
||||
if (dc->soc->supports_border_color)
|
||||
tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR);
|
||||
|
||||
tegra_dc_stats_reset(&dc->stats);
|
||||
|
||||
return 0;
|
||||
|
||||
cleanup:
|
||||
|
@ -1987,33 +1990,15 @@ static int tegra_dc_probe(struct platform_device *pdev)
|
|||
return PTR_ERR(dc->rst);
|
||||
}
|
||||
|
||||
reset_control_assert(dc->rst);
|
||||
|
||||
if (dc->soc->has_powergate) {
|
||||
if (dc->pipe == 0)
|
||||
dc->powergate = TEGRA_POWERGATE_DIS;
|
||||
else
|
||||
dc->powergate = TEGRA_POWERGATE_DISB;
|
||||
|
||||
err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk,
|
||||
dc->rst);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to power partition: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
err = clk_prepare_enable(dc->clk);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to enable clock: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = reset_control_deassert(dc->rst);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to deassert reset: %d\n",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
tegra_powergate_power_off(dc->powergate);
|
||||
}
|
||||
|
||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
|
@ -2027,16 +2012,19 @@ static int tegra_dc_probe(struct platform_device *pdev)
|
|||
return -ENXIO;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&dc->client.list);
|
||||
dc->client.ops = &dc_client_ops;
|
||||
dc->client.dev = &pdev->dev;
|
||||
|
||||
err = tegra_dc_rgb_probe(dc);
|
||||
if (err < 0 && err != -ENODEV) {
|
||||
dev_err(&pdev->dev, "failed to probe RGB output: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dc);
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
INIT_LIST_HEAD(&dc->client.list);
|
||||
dc->client.ops = &dc_client_ops;
|
||||
dc->client.dev = &pdev->dev;
|
||||
|
||||
err = host1x_client_register(&dc->client);
|
||||
if (err < 0) {
|
||||
dev_err(&pdev->dev, "failed to register host1x client: %d\n",
|
||||
|
@ -2044,8 +2032,6 @@ static int tegra_dc_probe(struct platform_device *pdev)
|
|||
return err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -2067,7 +2053,22 @@ static int tegra_dc_remove(struct platform_device *pdev)
|
|||
return err;
|
||||
}
|
||||
|
||||
reset_control_assert(dc->rst);
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int tegra_dc_suspend(struct device *dev)
|
||||
{
|
||||
struct tegra_dc *dc = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
err = reset_control_assert(dc->rst);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to assert reset: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (dc->soc->has_powergate)
|
||||
tegra_powergate_power_off(dc->powergate);
|
||||
|
@ -2077,10 +2078,45 @@ static int tegra_dc_remove(struct platform_device *pdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int tegra_dc_resume(struct device *dev)
|
||||
{
|
||||
struct tegra_dc *dc = dev_get_drvdata(dev);
|
||||
int err;
|
||||
|
||||
if (dc->soc->has_powergate) {
|
||||
err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk,
|
||||
dc->rst);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to power partition: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
err = clk_prepare_enable(dc->clk);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to enable clock: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = reset_control_deassert(dc->rst);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "failed to deassert reset: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops tegra_dc_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(tegra_dc_suspend, tegra_dc_resume, NULL)
|
||||
};
|
||||
|
||||
struct platform_driver tegra_dc_driver = {
|
||||
.driver = {
|
||||
.name = "tegra-dc",
|
||||
.of_match_table = tegra_dc_of_match,
|
||||
.pm = &tegra_dc_pm_ops,
|
||||
},
|
||||
.probe = tegra_dc_probe,
|
||||
.remove = tegra_dc_remove,
|
||||
|
|
|
@ -56,8 +56,8 @@ static void tegra_atomic_complete(struct tegra_drm *tegra,
|
|||
*/
|
||||
|
||||
drm_atomic_helper_commit_modeset_disables(drm, state);
|
||||
drm_atomic_helper_commit_planes(drm, state, false);
|
||||
drm_atomic_helper_commit_modeset_enables(drm, state);
|
||||
drm_atomic_helper_commit_planes(drm, state, true);
|
||||
|
||||
drm_atomic_helper_wait_for_vblanks(drm, state);
|
||||
|
||||
|
|
Loading…
Reference in a new issue