drm/tegra: dc: rgb: Allow changing PLLD rate on Tegra30+

Asus Transformer TF700T is a Tegra30 tablet device which uses RGB->DSI
bridge that requires a precise clock rate in order to operate properly.
Tegra30 has a dedicated PLL for each display controller, hence the PLL
rate can be changed freely. Allow PLL rate changes on Tegra30+ for RGB
output. Configure the clock rate before display controller is enabled
since DC itself may be running off this PLL and it's not okay to change
the rate of the active PLL that doesn't support dynamic frequency
switching since hardware will hang.

Tested-by: Maxim Schwalm <maxim.schwalm@gmail.com> #TF700T
Signed-off-by: Dmitry Osipenko <digetx@gmail.com>
Signed-off-by: Thierry Reding <treding@nvidia.com>
This commit is contained in:
Dmitry Osipenko 2021-09-30 01:28:05 +03:00 committed by Thierry Reding
parent 0f52fc3fc9
commit 0c921b6d4b
3 changed files with 65 additions and 12 deletions

View File

@ -1834,10 +1834,9 @@ int tegra_dc_state_setup_clock(struct tegra_dc *dc,
return 0;
}
static void tegra_dc_commit_state(struct tegra_dc *dc,
struct tegra_dc_state *state)
static void tegra_dc_set_clock_rate(struct tegra_dc *dc,
struct tegra_dc_state *state)
{
u32 value;
int err;
err = clk_set_parent(dc->clk, state->clk);
@ -1868,11 +1867,6 @@ static void tegra_dc_commit_state(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);
if (!dc->soc->has_nvdisplay) {
value = SHIFT_CLK_DIVIDER(state->div) | PIXEL_CLK_DIVIDER_PCD1;
tegra_dc_writel(dc, value, DC_DISP_DISP_CLOCK_CONTROL);
}
}
static void tegra_dc_stop(struct tegra_dc *dc)
@ -2074,6 +2068,9 @@ static void tegra_crtc_atomic_enable(struct drm_crtc *crtc,
u32 value;
int err;
/* apply PLL changes */
tegra_dc_set_clock_rate(dc, crtc_state);
err = host1x_client_resume(&dc->client);
if (err < 0) {
dev_err(dc->dev, "failed to resume: %d\n", err);
@ -2148,8 +2145,11 @@ static void tegra_crtc_atomic_enable(struct drm_crtc *crtc,
else
tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR);
/* apply PLL and pixel clock changes */
tegra_dc_commit_state(dc, crtc_state);
/* apply pixel clock changes */
if (!dc->soc->has_nvdisplay) {
value = SHIFT_CLK_DIVIDER(crtc_state->div) | PIXEL_CLK_DIVIDER_PCD1;
tegra_dc_writel(dc, value, DC_DISP_DISP_CLOCK_CONTROL);
}
/* program display mode */
tegra_dc_set_timings(dc, mode);
@ -2763,6 +2763,7 @@ static const struct tegra_dc_soc_info tegra20_dc_soc_info = {
.has_win_b_vfilter_mem_client = true,
.has_win_c_without_vert_filter = true,
.plane_tiled_memory_bandwidth_x2 = false,
.has_pll_d2_out0 = false,
};
static const struct tegra_dc_soc_info tegra30_dc_soc_info = {
@ -2785,6 +2786,7 @@ static const struct tegra_dc_soc_info tegra30_dc_soc_info = {
.has_win_b_vfilter_mem_client = true,
.has_win_c_without_vert_filter = false,
.plane_tiled_memory_bandwidth_x2 = true,
.has_pll_d2_out0 = true,
};
static const struct tegra_dc_soc_info tegra114_dc_soc_info = {
@ -2807,6 +2809,7 @@ static const struct tegra_dc_soc_info tegra114_dc_soc_info = {
.has_win_b_vfilter_mem_client = false,
.has_win_c_without_vert_filter = false,
.plane_tiled_memory_bandwidth_x2 = true,
.has_pll_d2_out0 = true,
};
static const struct tegra_dc_soc_info tegra124_dc_soc_info = {
@ -2829,6 +2832,7 @@ static const struct tegra_dc_soc_info tegra124_dc_soc_info = {
.has_win_b_vfilter_mem_client = false,
.has_win_c_without_vert_filter = false,
.plane_tiled_memory_bandwidth_x2 = false,
.has_pll_d2_out0 = true,
};
static const struct tegra_dc_soc_info tegra210_dc_soc_info = {
@ -2851,6 +2855,7 @@ static const struct tegra_dc_soc_info tegra210_dc_soc_info = {
.has_win_b_vfilter_mem_client = false,
.has_win_c_without_vert_filter = false,
.plane_tiled_memory_bandwidth_x2 = false,
.has_pll_d2_out0 = true,
};
static const struct tegra_windowgroup_soc tegra186_dc_wgrps[] = {
@ -2901,6 +2906,7 @@ static const struct tegra_dc_soc_info tegra186_dc_soc_info = {
.wgrps = tegra186_dc_wgrps,
.num_wgrps = ARRAY_SIZE(tegra186_dc_wgrps),
.plane_tiled_memory_bandwidth_x2 = false,
.has_pll_d2_out0 = false,
};
static const struct tegra_windowgroup_soc tegra194_dc_wgrps[] = {
@ -2951,6 +2957,7 @@ static const struct tegra_dc_soc_info tegra194_dc_soc_info = {
.wgrps = tegra194_dc_wgrps,
.num_wgrps = ARRAY_SIZE(tegra194_dc_wgrps),
.plane_tiled_memory_bandwidth_x2 = false,
.has_pll_d2_out0 = false,
};
static const struct of_device_id tegra_dc_of_match[] = {

View File

@ -76,6 +76,7 @@ struct tegra_dc_soc_info {
bool has_win_b_vfilter_mem_client;
bool has_win_c_without_vert_filter;
bool plane_tiled_memory_bandwidth_x2;
bool has_pll_d2_out0;
};
struct tegra_dc {

View File

@ -17,6 +17,8 @@ struct tegra_rgb {
struct tegra_output output;
struct tegra_dc *dc;
struct clk *pll_d_out0;
struct clk *pll_d2_out0;
struct clk *clk_parent;
struct clk *clk;
};
@ -119,6 +121,18 @@ static void tegra_rgb_encoder_enable(struct drm_encoder *encoder)
tegra_dc_commit(rgb->dc);
}
static bool tegra_rgb_pll_rate_change_allowed(struct tegra_rgb *rgb)
{
if (!rgb->pll_d2_out0)
return false;
if (!clk_is_match(rgb->clk_parent, rgb->pll_d_out0) &&
!clk_is_match(rgb->clk_parent, rgb->pll_d2_out0))
return false;
return true;
}
static int
tegra_rgb_encoder_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
@ -147,8 +161,17 @@ tegra_rgb_encoder_atomic_check(struct drm_encoder *encoder,
* and hope that the desired frequency can be matched (or at least
* matched sufficiently close that the panel will still work).
*/
div = ((clk_get_rate(rgb->clk) * 2) / pclk) - 2;
pclk = 0;
if (tegra_rgb_pll_rate_change_allowed(rgb)) {
/*
* Set display controller clock to x2 of PCLK in order to
* produce higher resolution pulse positions.
*/
div = 2;
pclk *= 2;
} else {
div = ((clk_get_rate(rgb->clk) * 2) / pclk) - 2;
pclk = 0;
}
err = tegra_dc_state_setup_clock(dc, crtc_state, rgb->clk_parent,
pclk, div);
@ -206,6 +229,22 @@ int tegra_dc_rgb_probe(struct tegra_dc *dc)
return err;
}
rgb->pll_d_out0 = clk_get_sys(NULL, "pll_d_out0");
if (IS_ERR(rgb->pll_d_out0)) {
err = PTR_ERR(rgb->pll_d_out0);
dev_err(dc->dev, "failed to get pll_d_out0: %d\n", err);
return err;
}
if (dc->soc->has_pll_d2_out0) {
rgb->pll_d2_out0 = clk_get_sys(NULL, "pll_d2_out0");
if (IS_ERR(rgb->pll_d2_out0)) {
err = PTR_ERR(rgb->pll_d2_out0);
dev_err(dc->dev, "failed to get pll_d2_out0: %d\n", err);
return err;
}
}
dc->rgb = &rgb->output;
return 0;
@ -213,9 +252,15 @@ int tegra_dc_rgb_probe(struct tegra_dc *dc)
int tegra_dc_rgb_remove(struct tegra_dc *dc)
{
struct tegra_rgb *rgb;
if (!dc->rgb)
return 0;
rgb = to_rgb(dc->rgb);
clk_put(rgb->pll_d2_out0);
clk_put(rgb->pll_d_out0);
tegra_output_remove(dc->rgb);
dc->rgb = NULL;