Re: [RFC] drm/tegra: Add a flag to mark that there is only one display pll

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 9/2/18 3:27 AM, r yang wrote:
> On Fri, Aug 31, 2018 at 11:44:11PM +0300, Dmitry Osipenko wrote:
>> On 8/31/18 10:31 PM, r yang wrote:
>>> On Wed, Aug 29, 2018 at 08:52:37PM +0300, Dmitry Osipenko wrote:
>>>> On 25.08.2018 04:11, r yang wrote:
>>>>> On Fri, Aug 24, 2018 at 05:40:11AM +0300, Dmitry Osipenko wrote:
>>>>>> On Friday 24 August 2018 03:53:22 ryang wrote:
>>>>>>> There is a workaround in which the tegra rgb driver initializes
>>>>>>> the tegra dc pclk to 0 so that it will skip setting the parent clk rate.
>>>>>>>
>>>>>>> The relevant commits:
>>>>>>> 3cebae6737b100323baca21de6bce6647249e778
>>>>>>> 76d59ed049197bdaaa24c0e061a105af6ce74457
>>>>>>>
>>>>>>> A more recent commit sets the rate of the dc clk itself:
>>>>>>> 39e08affecf0998be1b01f4752016e33fa98eb9a
>>>>>>>
>>>>>>> This doesn't make sense because it always sets the dc clk to 0.
>>>>>>> Is this intended behavior or does it just happen to be working for
>>>>>>> the current tegra 2 boards in the mainline kernel?
>>>>>>>
>>>>>>>
>>>>>>> For context I am running the kernel on a tegra 2 based
>>>>>>> Galaxy Tab 10.1. The display panel driver is out of tree.
>>>>>>> This panel has very low clock rate tolerances so it must be
>>>>>>> driven at a rate very close to the required specification (68.75Mhz).
>>>>>>> pll_p is not adequate to drive this panel so pll_d must be used.
>>>>>>>
>>>>>>> Another issue is the current workaround is always forcing the disp1
>>>>>>> clk to zero. The Galaxy Tab 10.1 locks up completely when calling
>>>>>>> clk_set_rate with a clk rate of 0 whether the parent is pll_p or
>>>>>>> pll_d.
>>>>>>>
>>>>>>>
>>>>>>> This patch adds a flag single_display_pll to mark that the device has
>>>>>>> only one display pll. This replaces the the pclk = 0 workaround.
>>>>>>>
>>>>>>> There is a comment in rgb.c about using the shift clock divider for
>>>>>>> tegra 2 but the divider is not set due to the has_nvdisplay flag.
>>>>>>> This patch also sets the shift clock divider based on the
>>>>>>> single_display_pll flag.
>>>>>>>
>>>>>>> A change I'm uncertain about. In tegra_dc_commit_state()
>>>>>>> the dc clock is now being set to the display panel rate rather than zero.
>>>>>>>
>>>>>>> I don't have any other tegra devices to test with. So I don't know
>>>>>>> if this breaks other devices. Previously the code was trying to set the
>>>>>>> clock rate to zero anyways.
>>>>>>>
>>>>>>> Signed-off-by: ryang <decatf@xxxxxxxxx>
>>>>>>> ---
>>>>>>>  drivers/gpu/drm/tegra/dc.c  | 9 +++++++--
>>>>>>>  drivers/gpu/drm/tegra/dc.h  | 1 +
>>>>>>>  drivers/gpu/drm/tegra/rgb.c | 1 -
>>>>>>>  3 files changed, 8 insertions(+), 3 deletions(-)
>>>>>>>
>>>>>>> diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
>>>>>>> index 965088afcfad..03f1ad630254 100644
>>>>>>> --- a/drivers/gpu/drm/tegra/dc.c
>>>>>>> +++ b/drivers/gpu/drm/tegra/dc.c
>>>>>>> @@ -1664,7 +1664,7 @@ static void tegra_dc_commit_state(struct tegra_dc
>>>>>>> *dc, * which is shared with other peripherals. Changing the clock rate *
>>>>>>> should therefore be avoided.
>>>>>>>  	 */
>>>>>>> -	if (state->pclk > 0) {
>>>>>>> +	if (!dc->soc->single_display_pll) {
>>>>>>
>>>>>> We don't want to change pll_p rate here. It will be better to check whether 
>>>>>> parent clock for disp is pll_d in tegra_rgb_encoder_atomic_check() and if it 
>>>>>> is the parent, then allow to propagate pclk into commit_state. We may request 
>>>>>> the pll_d using clk_get_sys(), then get the parent clock of disp by 
>>>>>> clk_get_parent(disp_clk) and finally compare the parent with pll_d using 
>>>>>> clk_is_math(parent, pll_d).
>>>>>
>>>>> Understood.
>>>>>
>>>>>>
>>>>>> Certainly parent clock selection could be more advanced, ideally the best 
>>>>>> parent clock should be selected in the atomic check and applied to disp clk 
>>>>>> on the commit by changing the disp's parent. So pll_d could be always 
>>>>>> selected if only one display controller is active at a time, otherwise the 
>>>>>> atomic check should resolve the parent clocks based on the required rates. It 
>>>>>> also could be permitable to adjust the pll_c rate.
>>>>>
>>>>> Right, the TRM describes quite a wide range of clock selection which currently
>>>>> aren't all possible to use on the kernel. The Galaxy Tab 10.1 stock kernel uses
>>>>> pll_c solely for disp1. Every clock normally on pll_c such as gr3d/gr2d/vde/etc
>>>>> are on pll_m. I suspect it was designed this way because the TRM says pll_d has
>>>>> a very power usage and that it should be avoided if possible.
>>>>>
>>>>> In absense of more advanced clock selection. It was my intention with this
>>>>> patch to try to at least have pll_p and pll_d working right for the internal
>>>>> display.
>>>>>
>>>>
>>>> Could you show the clock tree from downstream kernel? I'm curious what it looks
>>>> like, that should be in "/sys/kernel/debug/clock/clock_tree".
>>>>
>>>>
>>>> The patch below seems to work reasonably good, both HDMI and panel either are
>>>> working simultaneously or only panel works when both displays are running off
>>>> the pll_d. Could you give it a try?
>>>>
>>>>
>>>> diff --git a/drivers/gpu/drm/tegra/dc.c b/drivers/gpu/drm/tegra/dc.c
>>>> index bdc418bcd898..c91f1203418d 100644
>>>> --- a/drivers/gpu/drm/tegra/dc.c
>>>> +++ b/drivers/gpu/drm/tegra/dc.c
>>>> @@ -26,6 +26,33 @@
>>>>  #include <drm/drm_atomic_helper.h>
>>>>  #include <drm/drm_plane_helper.h>
>>>>
>>>> +unsigned int tegra_dc_div61(unsigned long  parent, unsigned long pixel)
>>>> +{
>>>> +	unsigned int rem, fract = 0;
>>>> +	unsigned int div61;
>>>> +	u64 integer;
>>>> +
>>>> +	if (parent <= pixel)
>>>> +		return 0;
>>>> +
>>>> +	integer = parent;
>>>> +	rem = do_div(integer, pixel);
>>>> +
>>>> +	if (rem > pixel / 4) {
>>>> +		if (rem > pixel * 2 / 3)
>>>> +			integer += 1;
>>>> +		else
>>>> +			fract = 1;
>>>> +	}
>>>> +
>>>> +	div61 = (integer << 1) | fract;
>>>> +
>>>> +	if (div61 > 255)
>>>> +		return 255;
>>>> +
>>>> +	return div61;
>>>> +}
>>>> +
>>>>  static void tegra_dc_stats_reset(struct tegra_dc_stats *stats)
>>>>  {
>>>>  	stats->frames = 0;
>>>> diff --git a/drivers/gpu/drm/tegra/dc.h b/drivers/gpu/drm/tegra/dc.h
>>>> index 382d38b1524b..dc3f681ddf1f 100644
>>>> --- a/drivers/gpu/drm/tegra/dc.h
>>>> +++ b/drivers/gpu/drm/tegra/dc.h
>>>> @@ -174,6 +174,7 @@ int tegra_dc_state_setup_clock(struct tegra_dc *dc,
>>>>  			       struct drm_crtc_state *crtc_state,
>>>>  			       struct clk *clk, unsigned long pclk,
>>>>  			       unsigned int div);
>>>> +unsigned int tegra_dc_div61(unsigned long  parent, unsigned long pixel);
>>>>
>>>>  /* from rgb.c */
>>>>  int tegra_dc_rgb_probe(struct tegra_dc *dc);
>>>> diff --git a/drivers/gpu/drm/tegra/hdmi.c b/drivers/gpu/drm/tegra/hdmi.c
>>>> index 0082468f703c..3c19af0eafe7 100644
>>>> --- a/drivers/gpu/drm/tegra/hdmi.c
>>>> +++ b/drivers/gpu/drm/tegra/hdmi.c
>>>> @@ -1450,10 +1450,13 @@ tegra_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
>>>>  	struct tegra_dc *dc = to_tegra_dc(conn_state->crtc);
>>>>  	unsigned long pclk = crtc_state->mode.clock * 1000;
>>>>  	struct tegra_hdmi *hdmi = to_hdmi(output);
>>>> +	unsigned long rate;
>>>>  	int err;
>>>>
>>>> +	rate = clk_round_rate(hdmi->clk_parent, pclk);
>>>> +
>>>>  	err = tegra_dc_state_setup_clock(dc, crtc_state, hdmi->clk_parent,
>>>> -					 pclk, 0);
>>>> +					 rate, tegra_dc_div61(rate, pclk));
>>>>  	if (err < 0) {
>>>>  		dev_err(output->dev, "failed to setup CRTC state: %d\n", err);
>>>>  		return err;
>>>> diff --git a/drivers/gpu/drm/tegra/rgb.c b/drivers/gpu/drm/tegra/rgb.c
>>>> index 28a78d3120bc..3ec3e13ce47f 100644
>>>> --- a/drivers/gpu/drm/tegra/rgb.c
>>>> +++ b/drivers/gpu/drm/tegra/rgb.c
>>>> @@ -19,6 +19,7 @@ struct tegra_rgb {
>>>>  	struct tegra_output output;
>>>>  	struct tegra_dc *dc;
>>>>
>>>> +	struct clk *pll_d_out0;
>>>>  	struct clk *clk_parent;
>>>>  	struct clk *clk;
>>>>  };
>>>> @@ -130,6 +131,8 @@ static void tegra_rgb_encoder_disable(struct drm_encoder
>>>> *encoder)
>>>>
>>>>  	if (output->panel)
>>>>  		drm_panel_unprepare(output->panel);
>>>> +
>>>> +	clk_rate_exclusive_put(rgb->clk);
>>>>  }
>>>>
>>>>  static void tegra_rgb_encoder_enable(struct drm_encoder *encoder)
>>>> @@ -165,6 +168,8 @@ static void tegra_rgb_encoder_enable(struct drm_encoder
>>>> *encoder)
>>>>
>>>>  	if (output->panel)
>>>>  		drm_panel_enable(output->panel);
>>>> +
>>>> +	clk_rate_exclusive_get(rgb->clk);
>>>>  }
>>>>
>>>>  static int
>>>> @@ -174,11 +179,17 @@ tegra_rgb_encoder_atomic_check(struct drm_encoder *encoder,
>>>>  {
>>>>  	struct tegra_output *output = encoder_to_output(encoder);
>>>>  	struct tegra_dc *dc = to_tegra_dc(conn_state->crtc);
>>>> -	unsigned long pclk = crtc_state->mode.clock * 1000;
>>>>  	struct tegra_rgb *rgb = to_rgb(output);
>>>> -	unsigned int div;
>>>> +	unsigned long pclk = crtc_state->mode.clock * 1000;
>>>> +	unsigned long rate;
>>>> +	bool pll_d_parent;
>>>>  	int err;
>>>>
>>>> +	if (clk_is_match(rgb->clk_parent, rgb->pll_d_out0))
>>>> +		pll_d_parent = true;
>>>> +	else
>>>> +		pll_d_parent = false;
>>>> +
>>>>  	/*
>>>>  	 * We may not want to change the frequency of the parent clock, since
>>>>  	 * it may be a parent for other peripherals. This is due to the fact
>>>> @@ -194,12 +205,17 @@ tegra_rgb_encoder_atomic_check(struct drm_encoder *encoder,
>>>>  	 * The best we can do at this point is to use the shift clock divider
>>>>  	 * and hope that the desired frequency can be matched (or at least
>>>>  	 * matched sufficiently close that the panel will still work).
>>>> +	 *
>>>> +	 * Make excuse for pll_d_out0 if it is explicitly set as a parent
>>>> +	 * for display panel.
>>>>  	 */
>>>> -	div = ((clk_get_rate(rgb->clk) * 2) / pclk) - 2;
>>>> -	pclk = 0;
>>>> +	if (pll_d_parent)
>>>> +		rate = clk_round_rate(rgb->clk_parent, pclk * 4);
>>>> +	else
>>>> +		rate = clk_get_rate(rgb->clk);
>>>>
>>>>  	err = tegra_dc_state_setup_clock(dc, crtc_state, rgb->clk_parent,
>>>> -					 pclk, div);
>>>> +					 rate, tegra_dc_div61(rate, pclk));
>>>>  	if (err < 0) {
>>>>  		dev_err(output->dev, "failed to setup CRTC state: %d\n", err);
>>>>  		return err;
>>>> @@ -254,6 +270,12 @@ 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)) {
>>>> +		dev_err(dc->dev, "failed to get pll_d_out0\n");
>>>> +		return PTR_ERR(rgb->pll_d_out0);
>>>> +	}
>>>> +
>>>>  	dc->rgb = &rgb->output;
>>>>
>>>>  	return 0;
>>>> @@ -265,6 +287,7 @@ int tegra_dc_rgb_remove(struct tegra_dc *dc)
>>>>  		return 0;
>>>>
>>>>  	tegra_output_remove(dc->rgb);
>>>> +	clk_put(to_rgb(dc->rgb)->pll_d_out0);
>>>>  	dc->rgb = NULL;
>>>>
>>>>  	return 0;
>>>
>>> I've tested the patch and it works. The disp1 will run off pll_d without
>>> any issues.
>>>
>>> Here is the clock tree from the downstream kernel.
>>
>> Okay, thank you. Now I think that it's better not to touch the display driver at
>> all, but change the PLLC clock to 586 MHz in the clock driver. There is no
>> appreciable difference in 600 vs 586 MHz, so that should be good enough.
>>
>> It would be ideal if clock rates could be specified via device tree, but that's
>> not possible today. So just change the PLLC rate globally in the clocks
>> init_table. Maybe later we'll manage to implement something that will allow to
>> set rates via device tree.
>>
> 
> There are two variants of the display panel chip for this tablet.
> They run at different clock rates:
> 
>  - 68.9Mhz divided down from 586Mhz
>  - 72Mhhz divided down from 570Mhz
> 
> With this in consideration I don't think it makes sense to change the
> initial pll_c rate.

The 72MHz variant will work perfectly with the PLLP as a parent. Have you tried
to use timings and rate of the 72MHz variant?

> Secondly, the issue with the workaround in the display driver still
> exists. The display driver is setting the display clock rate to zero
> in atomic check and applies the disp clock on the commit.
> This results in the device locking up.
> 
> It appears to me that there isn't a way to avoid the issue with this
> workaround.
> 
> I thought this lock up was due to the display panel chip in this
> particular tablet. I have discovered that the device will lock up if
> I set pll_d or pll_c clock rate to zero. And that this happens even
> if disp1 is not running off the pll.

Please show the clock tree of the upstream kernel from
"/sys/kernel/debug/clk/clk_summary".

> There is no lock up when trying to set pll_p rate to zero. I suspect
> this might be due to it being a fixed rate clock.
> Is this expected behavior? Can you reproduce this lock up?

Yes, PLLP is defined as fixed clock in the clock driver.

It is a bug in the DC driver. The disp clocks are registered with the
CLK_SET_RATE_PARENT flag, hence disp clock rate changes propagate to the parent.
Please feel free to send a patch, just remove the clk_set_rate(dc->clk,
state->pclk) from tegra_dc_commit_state().

> Is it not possible to go the route of your first proposed patch to check
> if the parent of display clock is pll_p?

It is possible. If you'll decide to choose that route, then please drop the
tegra_dc_div61() stuff as it is actually div71 and is not needed at all, I was
just trying something using it.

I think both variants are acceptable, though let's wait for the comment from
Thierry, as the last word will be after him.

>> Note that changing PLLC rate will makes sense only if you're going to upstream
>> the device tree for your device. Please prepare and submit the device tree and
>> clock patches. I think Thierry and Peter won't oppose to the variant with
>> changing rate of PLLC, for now that should be the best option.
>>
> 
> I would like to eventually upstream the device tree. There are only two things
> at the moment that are blocking this device from working on upstream kernel.
> One is this display clock initialization issue. The other is the
> non-standard partition table which required a patch from the Anrdoid kernels.
> For now I am using CONFIG_CMDLINE_PARTITION to specify the partition
> layout.

The partition layout shouldn't be a real stopper as seems there is external MMC.

IIRC, the custom partition is the ms-dos partition with a custom offsets, isn't
it? At least some of Tegra devices also have a spare GPT partition that is
specified by bootloader via gpt_sector argument of the kernels commandline.



[Index of Archives]     [ARM Kernel]     [Linux ARM]     [Linux ARM MSM]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux