[Freedreno] [PATCH v1 1/2] drm/msm/dp: enable HDP plugin/unplugged interrupts to hpd_enable/disable
Kuogee Hsieh
quic_khsieh at quicinc.com
Wed May 17 17:41:57 UTC 2023
On 5/16/2023 4:20 PM, Dmitry Baryshkov wrote:
> On 17/05/2023 01:35, Abhinav Kumar wrote:
>>
>>
>> On 5/16/2023 6:50 AM, Dmitry Baryshkov wrote:
>>> On 13/05/2023 00:28, Abhinav Kumar wrote:
>>>> Hi Bjorn and Dmitry
>>>>
>>>> On 5/12/2023 12:34 PM, Kuogee Hsieh wrote:
>>>>>
>>>>> On 5/12/2023 10:28 AM, Dmitry Baryshkov wrote:
>>>>>> On Fri, 12 May 2023 at 19:52, Kuogee Hsieh
>>>>>> <quic_khsieh at quicinc.com> wrote:
>>>>>>>
>>>>>>> On 5/11/2023 5:54 PM, Dmitry Baryshkov wrote:
>>>>>>>> On Fri, 12 May 2023 at 03:16, Kuogee Hsieh
>>>>>>>> <quic_khsieh at quicinc.com> wrote:
>>>>>>>>> On 5/11/2023 8:57 AM, Dmitry Baryshkov wrote:
>>>>>>>>>> On 11/05/2023 18:53, Bjorn Andersson wrote:
>>>>>>>>>>> On Thu, May 11, 2023 at 07:24:46AM +0300, Dmitry Baryshkov
>>>>>>>>>>> wrote:
>>>>>>>>>>>> On Wed, 10 May 2023 at 23:31, Kuogee Hsieh
>>>>>>>>>>>> <quic_khsieh at quicinc.com>
>>>>>>>>>>>> wrote:
>>>>>>>>>>>>> The internal_hpd flag was introduced to handle external DP
>>>>>>>>>>>>> HPD
>>>>>>>>>>>>> derived from GPIO
>>>>>>>>>>>>> pinmuxed into DP controller. HPD plug/unplug interrupts
>>>>>>>>>>>>> cannot be
>>>>>>>>>>>>> enabled until
>>>>>>>>>>>>> internal_hpd flag is set to true.
>>>>>>>>>>>>> At both bootup and resume time, the DP driver will enable
>>>>>>>>>>>>> external DP
>>>>>>>>>>>>> plugin interrupts and handle plugin interrupt accordingly.
>>>>>>>>>>>>> Unfortunately
>>>>>>>>>>>>> dp_bridge_hpd_enable() bridge ops function was called to set
>>>>>>>>>>>>> internal_hpd
>>>>>>>>>>>>> flag to true later than where DP driver expected during
>>>>>>>>>>>>> bootup time.
>>>>>>>>>>>>>
>>>>>>>>>>>>> This causes external DP plugin event to not get detected and
>>>>>>>>>>>>> display stays blank.
>>>>>>>>>>>>> Move enabling HDP plugin/unplugged interrupts to
>>>>>>>>>>>>> dp_bridge_hpd_enable()/disable() to
>>>>>>>>>>>>> set internal_hpd to true along with enabling HPD
>>>>>>>>>>>>> plugin/unplugged
>>>>>>>>>>>>> interrupts
>>>>>>>>>>>>> simultaneously to avoid timing issue during bootup and
>>>>>>>>>>>>> resume.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Fixes: cd198caddea7 ("drm/msm/dp: Rely on hpd_enable/disable
>>>>>>>>>>>>> callbacks")
>>>>>>>>>>>>> Signed-off-by: Kuogee Hsieh <quic_khsieh at quicinc.com>
>>>>>>>>>>>> Thanks for debugging this!
>>>>>>>>>>>>
>>>>>>>>>>>> However after looking at the driver I think there is more
>>>>>>>>>>>> than this.
>>>>>>>>>>>>
>>>>>>>>>>>> We have several other places gated on internal_hpd flag,
>>>>>>>>>>>> where we do
>>>>>>>>>>>> not have a strict ordering of events.
>>>>>>>>>>>> I see that dp_hpd_plug_handle() and dp_hpd_unplug_handle()
>>>>>>>>>>>> also toggle
>>>>>>>>>>>> DP_DP_IRQ_HPD_INT_MASK and DP_DP_HPD_REPLUG_INT_MASK
>>>>>>>>>>>> depending on
>>>>>>>>>>>> internal_hpd. Can we toggle all 4 interrupts from the
>>>>>>>>>>>> hpd_enable/hpd_disable functions? If we can do it, then I
>>>>>>>>>>>> think we can
>>>>>>>>>>>> drop the internal_hpd flag completely.
>>>>>>>>>>>>
>>>>
>>>> No we cannot. The HPD logic works in a flip-flop model. When we get
>>>> the plug interrupt, we need to flip to tell the hw to wait for
>>>> unplug and when we get unplug, we need to tell the hw to wait for
>>>> plug.
>>>
>>> But, doesn't dp_display_config_hpd() (current code) or
>>> dp_bridge_hpd_enable() (after this patch) enable both plug and
>>> unplug interrupts? This doesn't fit well into the flip-flop
>>> description.
>>>
>>
>> Let me clarify / correct the response. Ideally thats what is usually
>> done to wait for disconnect when we get connect and vice-versa. HDMI
>> still does it the same way.
>>
>> https://gitlab.freedesktop.org/drm/msm/-/blob/msm-next/drivers/gpu/drm/msm/hdmi/hdmi_hpd.c#L196
>>
>
> So, HDMI HPD is real flip-flop, sounds fine.
>
>>
>> But I checked with kuogee that DP always enabled HPD connect and
>> disconnect interrupts by default and he mentioned its mainly because
>> we wanted to enable HPD connect / disconnect by default but not the
>> others.
>>
>> That being said, the logic is close to flip-flop that when you get a
>> connect event, you wait for the "other" interrupts which are possible
>> which is IRQ_HPD and REPLUG and during disconnect, those are not
>> possible so disable them. Thats why the calls to
>> dp_catalog_hpd_config_intr() are present in the plug_handle /
>> unplug_handle to enable the "other possible" interrupts.
>
> Can we keep them always enabled? Are these interrupts edge-triggered
> or level-triggered? What prevents us from enabling these interrupts
> all the time? Or enabling all 4 interrupts in hpd_enable() and
> disabling them in hpd_disable()? Can IRQ_HPD or REPLUG fire when the
> cable is disconnected?
> 1) edge-trigger at hpd pin low. However hpd block will start
> debouncing logic and set status bit (plug, unplug, hpd_irq ore replug)
> properly base on the result of debunce.
2) there should be fine to have all interrupts enabled
3) IRQ_HPD and REPLUG will not happen when disconnected.
>>
>> The logic from dp_display_config_hpd() is getting removed in this
>> patch, in case you didnt check to align just with hpd_enable /
>> hpd_disable callbacks.
>
> I saw this. The code is being moved to dp_bridge_hpd_enable(), as I
> mentioned in the email.
>
>>
>>>>
>>>> The two calls in plug_handle() / unplug_handle() are doing that
>>>> whereas hpd_enable/hpd_disable are disabling the hpd interrupts
>>>> altogether.
>>>>
>>>> In other words, we cannot rely on hpd_enable() / hpd_disable()
>>>> calls to do the flip part as that has to be done every plug/unplug.
>>>> In addition we need to handle the compliance test cases with REPLUG.
>>>
>>> Thank you for the explanation. Would it be possible to keep
>>> mask/unmask, but make hpd_enable/hpd_disable toggle
>>> DP_DP_HPD_CTRL_HPD_EN instead?
>>>
>>
>> Yes, this should be possible but we would like to test this. But what
>> about the interrupt masks then. So you are saying, hpd_enable will
>> only toggle the DP_DP_HPD_CTRL_HPD_EN but leave the HPD connect and
>> disconnect interrupts intact? That also doesnt sound right.
>>
>> enabling the block all the time and then toggling the interrupt masks
>> seemed like a better thing.
>
> Why? We do not need the block outside of the
> hpd_enable()/hpd_disable() pair. Even from the power consumption
> perspective, disabling the unused block sounds better to me.
We are mean within the block of hpd_enabled and hdp_disabled pair,
At hpd_enabled, we will do both item 1) and 2) instead of just only item
1) as you mentioned. you still need power on hpd block to just do item2).
1) enabled DP_DP_HPD_CTRL_HPD_EN
2) enable HDP interrupts (plug, unplug, hpd_irq and replug)
>>
>>>>
>>>> So hpd_enable / hpd_disable is not the right place to move all
>>>> these calls.
>>>>
>>>>>>>>>>> Yes, that's what I believe the DRM framework intend us to do.
>>>>>>>>>>>
>>>>>>>>>>> The problem, and reason why I didn't do tat in my series,
>>>>>>>>>>> was that in
>>>>>>>>>>> order to update the INT_MASKs you need to clock the IP-block
>>>>>>>>>>> and that's
>>>>>>>>>>> done elsewhere.
>>>>>>>>>>>
>>>>>>>>>>> So, for the internal_hpd case, it seems appropriate to
>>>>>>>>>>> pm_runtime_get()
>>>>>>>>>>> in hpd_enable() and unmask the HPD interrupts, and mask the
>>>>>>>>>>> interrupts
>>>>>>>>>>> and pm_runtime_put() in hpd_disable().
>>>>>>>>>>>
>>>>
>>>> I dont agree completely on this. The blocks powering the HPD block
>>>> also power other things. So the AUX clk and host_init() both are
>>>> needed for HPD but they power not just the HPD.
>>>>
>>>> So powering all of these at hpd_enable / hpd_disable would be an
>>>> overkill for that call and not required as well.
>>>
>>> The main requirement behind hpd_enable/disable is that:
>>> - hpd_enable makes sure that hpd events are properly detected and
>>> delivered afterwards.
>>> - hpd_disable must unroll this. In other words, "Once
>>> [..hpd_disable..] returns the bridge shall not call
>>> drm_bridge_hpd_notify()".
>>>
>>
>> Yes, we are aware of it and this patch keeps that documented
>> expectation intact. AFAICT, we want to skip dp_bridge_hpd_notify in
>> the internal hpd case. Please note, like I wrote before, these
>> callbacks are listed as optional
>>
>> 632 * This callback is optional and shall only be implemented by
>> bridges
>> 633 * that support hot-plug notification without polling.
>> Bridges that
>> 634 * implement it shall also implement the @hpd_disable
>> callback and set
>> 635 * the DRM_BRIDGE_OP_HPD flag in their &drm_bridge->ops.
>> 636 */
>> 637 void (*hpd_enable)(struct drm_bridge *bridge);
>> 638
>>
>> So, the intention of introducing them to DP driver was to handle the
>> case which Bjorn listed. Otherwise things are fine the way it is
>> because like Stephen wrote, whether to use internal_hpd or not is a
>> probe time decision, we really dont need to tie it with
>> hpd_enable/hpd_disable callbacks.
>
> Small correction correct. This is not a probe-time decision. One can
> not decide this just basing on the next bridge existence.
> drm_bridge_connector decides which bridges to use for HPD or DETECT at
> the drm_bridge_connector_init(), which happens duing dpu_kms_hw_init().
>
> hpd_enable/disable are optional (the drm core can function without
> them being provided). However having them implemented is still useful.
> It allows one to disable unused logic.
>
>>
>>>> Before talking about removing hpd_event_thread, I think we should
>>>> understand why its there. It handles all asynchronous connection
>>>> and sink related events in one centralized place like
>>>> connect/disconnect/irq_hpd.
>>>>
>>>> This is a well tested code with multiple dongles on chromebooks and
>>>> I dont see any alternative to it at the moment and dont think that
>>>> discussion is necessary in the context of this bug fix . We can
>>>> talk about it more in one of our sync ups if you want to know more.
>>>
>>> Sync ups are private. Can we please keep this discussion public? It
>>> would be beneficial for other parties too (e.g. ChromeOS people).
>>>
>>
>>> The main issue with the current event thread is that it short cuts
>>> all HPD handling. This causes some uevents not being sent to the
>>> userspace, etc.
>>>
>>> Please see below.
>>
>> Sure, but questioning why it exists and that it can be removed
>> distracts folks from the objective of fixing the hpd issue.
>>
>> I have some questions about your assessment of uevents not being sent
>> to userspace . Answers below to that.
>>
>>>
>>>
>>>> Based on the responses I have seen so far, I see that we had to
>>>> introduce the dynamic control of internal_hpd for below case :
>>>>
>>>> 2) DP without GPIO: Downstream drm_bridge connected, is_edp = false
>>>> and
>>>> internal HPD-logic should not be used/enabled (internal_hpd = false).
>>>> Power doesn't need to be on unless hpd_notify is invoked to tell us
>>>> that
>>>> there's something connected...
>>>
>>> Ideally, the DP block should be in the runtime suspension state,
>>> unless one of the following happens:
>>>
>>> - hpd_enable() was called without consequent hpd_disable() call
>>> - atomic_pre_enable() was called to start up the stream,
>>> atomic_post_disable() was not called yet
>>> - get_edid() / get_modes() is in progress
>>> - detect() is in progress
>>> - eDP? I admit I do not fully understand the requirements there, so
>>> Stephen / Doug might be in a better position to comment about it
>>>
>>
>> This list is correct except for the other aspect I explained.
>>
>> Like you mentioned, we really need to power up the hpd block only
>> when hpd_enable is called. But to power up the hpd block, we need:
>>
>> -> AUX clk
>> -> mdss ahb clk
>> -> program hpd registers
>>
>> We need to carefully isolate these out to hpd_enable / hpd_disable.
>> This can be tried but will only delay this fix longer ... We can give
>> it a shot.
>
> I'd start with the simpler approach. We have AUX and MDSS AHB clocks
> enabled all the time. I'm suggesting to limit (for the fix)
> hpd_enable()/disable() to HPD register programming, if that is easier
> ATM. Power optimizations can come later, provided everybody
> understands them. If AUX or AHB clocks programming is easy, them I'm
> fine with them getting in at the same time.
>
>> All these concerns should have been thought of when actually
>> introducing the internal_hpd flag and the hpd_enable / hpd_disable
>> callbacks. But looks like neither sides did.
>
> Yes. Quite unfortunate, but true.
>
>>
>> Anyway, like we always do, we can attempt cleaning this up like the
>> way i have explained above to move the enable / disable of those 3
>> resources to hpd_enable / hpd_disable. Hopefully it works and is a
>> small change.
>>
>>>>
>>>> So we want internal_hpd to be false for this case.
>>>>
>>>> That is good information and provides the context of why hpd_enable
>>>> / hpd_disable callbacks were implemented as they are optional as
>>>> per the framework.
>>>>
>>>> I saw Bjorn mentioned that "The DRM framework will invoke
>>>> hpd_enable on the bridge furthest out that
>>>> has OP_HPD. So in the case of HPD signal being pinmuxed into the
>>>> HPD-logic, dp_bridge_hpd_enable() will be invoked.
>>>> "
>>>>
>>>> For my understanding, this logic is in the
>>>> drm_bridge_connector_init() right? So
>>>> bridge_connector->bridge_detect will hold the last bridge and hence
>>>> enable_hpd / disable/hpd gets called only for the last one right?
>>>
>>> You are mixing bridge_detect and bridge_hpd here. But yes, these
>>> pointers are initialized to the last bridges implementing
>>> correspondingly OP_DETECT / OP_HPD. Then
>>> drm_bridge_connector_detect() will call bridge_detect->detect().
>>> drm_bridge_connector_enable_hpd() will call bridge_hpd->hpd_enable.
>>>
>>
>> Thanks for confirming.
>>
>>> Note, the hpd_notify() callback will be called for all bridges in a
>>> chanin.
>>>
>>>>
>>>> If all this is correct, I think the fix posted at the moment is the
>>>> best possible one as it correctly does what hpd_enable /
>>>> hpd_disable callbacks are supposed to do without overdoing it.
>>>>
>>>> What are the concerns with this patch with all the explanation I
>>>> have given now.
>>>
>>> I'd like to better understand the flip-flop story and the
>>> DP_DP_HPD_CTRL_HPD_EN.
>>>
>>> Also, as you can see, the discussion of this patch popped up
>>> discussions for two other problems:
>>> - power consumption / pm_runtime status
>>> - HPD notifications
>>>
>>
>> pm_runtime status is really not affected with this patch.
>>
>> Even without this patch, the dp_display_host_deinit was called
>> dp_pm_suspend which would have powered down the HPD related resources.
>>
>> hpd_enable / hpd_disable callbacks from the DRM fwk only provide us
>> another hook to do something which was already being done.
>
> There was a point from Bjorn that there is no need to power on the DP
> block, if the output is disabled and HPD is not enabled. It was not
> directly related to this patch, but (in my opinion) it was an attempt
> to understand and optimize the logic.
>
>>
>> HPD notifications explained below.
>>
>>>>
>>>>>>>>>>>
>>>>>>>>>>> But for edp and external HPD-signal we also need to make
>>>>>>>>>>> sure power is
>>>>>>>>>>> on while something is connected...
>>>>>>>>>> I think this is already handled by the existing code, see
>>>>>>>>>> calls to the
>>>>>>>>>> dp_display_host_init().
>>>>>>>>>>
>>>>>>>>>>>> I went on and checked other places where it is used:
>>>>>>>>>>>> - dp_hpd_unplug_handle(), guarding DP_DP_HPD_PLUG_INT_MASK
>>>>>>>>>>>> toggling. I
>>>>>>>>>>>> think we can drop these two calls completely. The function
>>>>>>>>>>>> is under
>>>>>>>>>>>> the event_mutex protection, so other events can not interfere.
>>>>>>>>>>>> - dp_bridge_hpd_notify(). What is the point of this check?
>>>>>>>>>>>> If some
>>>>>>>>>>>> other party informs us of the HPD event, we'd better handle
>>>>>>>>>>>> it instead
>>>>>>>>>>>> of dropping it. Correct? In other words, I'd prefer seeing
>>>>>>>>>>>> the
>>>>>>>>>>>> hpd_event_thread removal. Instead of that I think that on
>>>>>>>>>>>> HPD/plug/unplug/etc. IRQ the driver should call into the
>>>>>>>>>>>> drm stack,
>>>>>>>>>>>> then the hpd_notify call should process those events.
>>>
>>> Now about the hpd_event_thread and the callbacks.
>>>
>>> Currently the driver shortcuts the DRM infrastructure and tries to
>>> handle all the details / call sequences. This might be partially
>>> correct, but it definitely needs to be improved:
>>>
>>> drm_bridge_connector_hpd_cb() also calls
>>> drm_kms_helper_hotplug_event(), which does other imporant things
>>> like sending uevent, notifying drm clients, etc. This part is
>>> completely missing for the internal_hpd case in the current driver.
>>>
>>
>> Today the DP driver calls drm_helper_hpd_irq_event to notify events
>> which calls drm_kms_helper_hotplug_event internally. So what part is
>> missing?
>
> Ack, I missed this call beneath all the event processing.
>
> However it still misses a call to drm_bridge_connector_hpd_notify() to
> notify all the bridges in a chain. This might become important later
> for the redrivers.
>
> Not to mention that drm_helper_hpd_irq_event() loops all connectors to
> check which ones were changed. For DP driver this is not required. We
> already know the exact connector and new state, so the loop is
> unnecessary.
>
>>
>>> Thus I suggest the following refactoring:
>>>
>>> - On all HPD events the driver should call drm_bridge_hpd_notify().
>>> For the REPLUG it might be required to perform two notifications in
>>> a sequence.
>>>
>>
>> I am seeing this from users in drm/bridge (that is external hpd)
>> which makes sense. So if I understand this better, in external_hpd
>> case I do see its purpose as this info has to be passed down from the
>> bridge to the Display controller through the drm fwk, not for the
>> internal_hpd case because in that case the controller already knows
>> as it is the one generating this interrupt.
>
> With the help of drm_bridge_hpd_notify() and proper hpd_notify() we
> can unify both cases. The driver just gets the
> connected/disconnected/attention events and handles them. It should
> not matter, which part generates them.
>
>>
>>> - drm_bridge_connector_hpd_cb() should be taught to pass through the
>>> (old_status == status) events (either in all cases or if the driver
>>> requests that)
>>>
>>> - The dp_bridge_hpd_notify() will be called for all HPD events. This
>>> way it becomes natural to remove the !internal_hpd check from this
>>> function and handle all HPD events from the proper single place,
>>> hpd_notify.
>>>
>>>
>>
>>>>>>>>>>>>
>>>>
>>>>>>>>> 1) DP with GPIO: No downstream drm_bridge are connected,
>>>>>>>>> is_edp = false
>>>>>>>>> and internal HPD-logic is in used (internal_hpd = true). Power
>>>>>>>>> needs to
>>>>>>>>> be on at all times etc.
>>>>>>>>>
>>>>>>>>> 2) DP without GPIO: Downstream drm_bridge connected, is_edp =
>>>>>>>>> false and
>>>>>>>>> internal HPD-logic should not be used/enabled (internal_hpd =
>>>>>>>>> false).
>>>>>>>>> Power doesn't need to be on unless hpd_notify is invoked to
>>>>>>>>> tell us that
>>>>>>>>> there's something connected...
>>>>>>>>>
>>>>>>>>> - dp_bridge_hpd_notify(). What is the point of this check? <==
>>>>>>>>> i have
>>>>>>>>> below two questions,
>>>>>>>>>
>>>>>>>>> 1) can you explain when/what this dp_bridge_hpd_notify() will
>>>>>>>>> be called?
>>>>>>>> The call chain is drm_bridge_hpd_notify() ->
>>>>>>>> drm_bridge_connector_hpd_notify() -> .hpd_notify() for all
>>>>>>>> drm_bridge
>>>>>>>> in chain
>>>>>>>>
>>>>>>>> One should add a call to drm_bridge_hpd_notify() when the hotplug
>>>>>>>> event has been detected.
>>>>>>>>
>>>>>>>> Also please note the patch
>>>>>>>> https://patchwork.freedesktop.org/patch/484432/
>>>>>>>>
>>>>>>>>> 2) is dp_bridge_hpd_notify() only will be called at above case
>>>>>>>>> #2? and
>>>>>>>>> it will not be used by case #1?
>>>>>>>> Once the driver calls drm_bridge_hpd_notify() in the hpd path, the
>>>>>>>> hpd_notify callbacks will be called in case#1 too.
>>>>>>>>
>>>>>>>> BTW: I don't see drm_bridge_hpd_notify() or
>>>>>>>> drm_kms_{,connector_}_hotplug_event() HPD notifications in the DP
>>>>>>>> driver at all. This should be fixed.
>>>>>>> Just curious, since dp_bridge_detect() only return either
>>>>>>> connector_status_connected or connector_status_disconnect,
>>>>>>>
>>>>>>> how IRQ_HPD_INT (attention) and HPD_REPLUG_INT be generated at
>>>>>>> DP case#1?
>>>>>> if (bridge.status == connected && status == connected) {
>>>>>> either attention or replug were reported
>>>>>> }
>>>>>>
>>>>>> BTW: what is HPD_REPLUG_INT, if you excuse my ignorance?
>>>>>
>>>>> HPD high -- drop to low for less than 2 ms -- go back to high again
>>>>>
>>>>> Currently, we have to treat this scenario as HPD_UNPLUG_int
>>>>> followed by HPD_PLUG_INT to pass compliance test
>>>>>
>>>>>>
>>>>>>>>>
>>>>>>>>>>> I agree, that seems to be what's expected of us from the DRM
>>>>>>>>>>> framework.
>>>
>>>
>
More information about the Freedreno
mailing list