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
@ -10,6 +10,7 @@
|
|||||||
#include <linux/clk.h>
|
#include <linux/clk.h>
|
||||||
#include <linux/debugfs.h>
|
#include <linux/debugfs.h>
|
||||||
#include <linux/iommu.h>
|
#include <linux/iommu.h>
|
||||||
|
#include <linux/pm_runtime.h>
|
||||||
#include <linux/reset.h>
|
#include <linux/reset.h>
|
||||||
|
|
||||||
#include <soc/tegra/pmc.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);
|
tegra_dc_stats_reset(&dc->stats);
|
||||||
drm_crtc_vblank_off(crtc);
|
drm_crtc_vblank_off(crtc);
|
||||||
|
|
||||||
|
pm_runtime_put_sync(dc->dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tegra_crtc_enable(struct drm_crtc *crtc)
|
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);
|
struct tegra_dc *dc = to_tegra_dc(crtc);
|
||||||
u32 value;
|
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);
|
tegra_dc_commit_state(dc, state);
|
||||||
|
|
||||||
/* program display mode */
|
/* program display mode */
|
||||||
@ -1685,7 +1730,6 @@ static int tegra_dc_init(struct host1x_client *client)
|
|||||||
struct tegra_drm *tegra = drm->dev_private;
|
struct tegra_drm *tegra = drm->dev_private;
|
||||||
struct drm_plane *primary = NULL;
|
struct drm_plane *primary = NULL;
|
||||||
struct drm_plane *cursor = NULL;
|
struct drm_plane *cursor = NULL;
|
||||||
u32 value;
|
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
dc->syncpt = host1x_syncpt_request(dc->dev, flags);
|
dc->syncpt = host1x_syncpt_request(dc->dev, flags);
|
||||||
@ -1755,47 +1799,6 @@ static int tegra_dc_init(struct host1x_client *client)
|
|||||||
goto cleanup;
|
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;
|
return 0;
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
@ -1987,33 +1990,15 @@ static int tegra_dc_probe(struct platform_device *pdev)
|
|||||||
return PTR_ERR(dc->rst);
|
return PTR_ERR(dc->rst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reset_control_assert(dc->rst);
|
||||||
|
|
||||||
if (dc->soc->has_powergate) {
|
if (dc->soc->has_powergate) {
|
||||||
if (dc->pipe == 0)
|
if (dc->pipe == 0)
|
||||||
dc->powergate = TEGRA_POWERGATE_DIS;
|
dc->powergate = TEGRA_POWERGATE_DIS;
|
||||||
else
|
else
|
||||||
dc->powergate = TEGRA_POWERGATE_DISB;
|
dc->powergate = TEGRA_POWERGATE_DISB;
|
||||||
|
|
||||||
err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk,
|
tegra_powergate_power_off(dc->powergate);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||||
@ -2027,16 +2012,19 @@ static int tegra_dc_probe(struct platform_device *pdev)
|
|||||||
return -ENXIO;
|
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);
|
err = tegra_dc_rgb_probe(dc);
|
||||||
if (err < 0 && err != -ENODEV) {
|
if (err < 0 && err != -ENODEV) {
|
||||||
dev_err(&pdev->dev, "failed to probe RGB output: %d\n", err);
|
dev_err(&pdev->dev, "failed to probe RGB output: %d\n", err);
|
||||||
return 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);
|
err = host1x_client_register(&dc->client);
|
||||||
if (err < 0) {
|
if (err < 0) {
|
||||||
dev_err(&pdev->dev, "failed to register host1x client: %d\n",
|
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;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
platform_set_drvdata(pdev, dc);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2067,7 +2053,22 @@ static int tegra_dc_remove(struct platform_device *pdev)
|
|||||||
return err;
|
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)
|
if (dc->soc->has_powergate)
|
||||||
tegra_powergate_power_off(dc->powergate);
|
tegra_powergate_power_off(dc->powergate);
|
||||||
@ -2077,10 +2078,45 @@ static int tegra_dc_remove(struct platform_device *pdev)
|
|||||||
return 0;
|
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 = {
|
struct platform_driver tegra_dc_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "tegra-dc",
|
.name = "tegra-dc",
|
||||||
.of_match_table = tegra_dc_of_match,
|
.of_match_table = tegra_dc_of_match,
|
||||||
|
.pm = &tegra_dc_pm_ops,
|
||||||
},
|
},
|
||||||
.probe = tegra_dc_probe,
|
.probe = tegra_dc_probe,
|
||||||
.remove = tegra_dc_remove,
|
.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_modeset_disables(drm, state);
|
||||||
drm_atomic_helper_commit_planes(drm, state, false);
|
|
||||||
drm_atomic_helper_commit_modeset_enables(drm, state);
|
drm_atomic_helper_commit_modeset_enables(drm, state);
|
||||||
|
drm_atomic_helper_commit_planes(drm, state, true);
|
||||||
|
|
||||||
drm_atomic_helper_wait_for_vblanks(drm, state);
|
drm_atomic_helper_wait_for_vblanks(drm, state);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user