From c4f67e212a084e0f100a672bf4bfe0b6aea61953 Mon Sep 17 00:00:00 2001 From: Leon Silcott Date: Sat, 14 Nov 2026 05:07:43 +0000 Subject: [PATCH 1/2] drm/msm/dp: port AUX ownership and display state machine from 7.2 research Port the DP cluster rework from the 7.2 overlay (507356d03f3c) to the current 7.3 tree: explicit AUX transfer ownership with fault draining, state-lock-serialized ctrl transitions, audio restart/stop entrypoints, and the rewritten dp_display host lifecycle. Adaptations for the 7.3 base: - config_ctrl keeps the RMW clear of stream-specific fields from the #73 stream split (commit 7de83c21291f), layered under the overlay rework. - bridge ops switch from .atomic_reset to .atomic_create_state (base commit 65dc3b473bb5c removed atomic_reset support). - container_of and long-line wraps to keep checkpatch line-length clean. Build-verified on the Surface Pro 11 (native arm64 gcc 15.2, shipped 7.3.0-jg-1sp11v4 source + Ubuntu config): all eight dp/*.o plus the full msm/ directory compile with zero warnings or errors. Signed-off-by: Leon Silcott --- drivers/gpu/drm/msm/dp/dp_audio.c | 164 ++- drivers/gpu/drm/msm/dp/dp_audio.h | 19 +- drivers/gpu/drm/msm/dp/dp_aux.c | 289 +++++- drivers/gpu/drm/msm/dp/dp_aux.h | 31 +- drivers/gpu/drm/msm/dp/dp_ctrl.c | 992 ++++++++++++------ drivers/gpu/drm/msm/dp/dp_ctrl.h | 46 +- drivers/gpu/drm/msm/dp/dp_display.c | 1481 ++++++++++++++++++++------- drivers/gpu/drm/msm/dp/dp_display.h | 11 - drivers/gpu/drm/msm/dp/dp_drm.c | 54 +- drivers/gpu/drm/msm/dp/dp_drm.h | 12 + drivers/gpu/drm/msm/dp/dp_panel.c | 84 +- drivers/gpu/drm/msm/dp/dp_panel.h | 17 +- 12 files changed, 2374 insertions(+), 826 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_audio.c b/drivers/gpu/drm/msm/dp/dp_audio.c index af7b72dd896c7a..561980385dc700 100644 --- a/drivers/gpu/drm/msm/dp/dp_audio.c +++ b/drivers/gpu/drm/msm/dp/dp_audio.c @@ -6,12 +6,15 @@ #define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ +#include #include +#include #include #include #include "dp_audio.h" +#include "dp_ctrl.h" #include "dp_drm.h" #include "dp_panel.h" #include "dp_reg.h" @@ -22,8 +25,12 @@ struct msm_dp_audio_private { struct platform_device *pdev; struct drm_device *drm_dev; void __iomem *link_base; + struct mutex *state_lock; + struct msm_dp_ctrl *ctrl; u32 channels; + /* ALSA intent survives a video modeset, unlike engine enable ownership. */ + bool prepared; struct msm_dp_audio msm_dp_audio; }; @@ -265,41 +272,123 @@ static struct msm_dp_audio_private *msm_dp_audio_get_data(struct msm_dp *msm_dp_ return container_of(msm_dp_audio, struct msm_dp_audio_private, msm_dp_audio); } +static bool msm_dp_audio_ready(struct msm_dp_audio_private *audio, + struct msm_dp *msm_dp_display) +{ + lockdep_assert_held(audio->state_lock); + return msm_dp_display->power_on && + msm_dp_ctrl_link_regs_accessible(audio->ctrl) && + msm_dp_ctrl_has_power_ref(audio->ctrl) && + !msm_dp_ctrl_has_phy_error(audio->ctrl); +} + +static void msm_dp_audio_program(struct msm_dp_audio_private *audio) +{ + msm_dp_audio_setup_sdp(audio); + msm_dp_audio_setup_acr(audio); + msm_dp_audio_safe_to_exit_level(audio); + msm_dp_audio_enable(audio, true); +} + +int msm_dp_audio_restart(struct msm_dp *msm_dp_display) +{ + struct msm_dp_audio_private *audio = msm_dp_audio_get_data(msm_dp_display); + + if (IS_ERR(audio)) + return PTR_ERR(audio); + lockdep_assert_held(audio->state_lock); + if (!audio->prepared) + return 0; + if (!msm_dp_audio_ready(audio, msm_dp_display) || !audio->channels) + return -EIO; + if (!msm_dp_display->audio_enabled) { + msm_dp_audio_program(audio); + msm_dp_display->audio_enabled = true; + } + return 0; +} + +int msm_dp_audio_stop(struct msm_dp *msm_dp_display) +{ + struct msm_dp_audio_private *audio; + + audio = msm_dp_audio_get_data(msm_dp_display); + if (IS_ERR(audio)) + return PTR_ERR(audio); + + lockdep_assert_held(audio->state_lock); + if (!msm_dp_display->audio_enabled) + return 0; + /* Preserve the successful-enable ledger if HW is no longer accessible. */ + if (!msm_dp_ctrl_link_regs_accessible(audio->ctrl)) + return -EIO; + + msm_dp_audio_enable(audio, false); + msm_dp_display->audio_enabled = false; + return 0; +} + +static void msm_dp_audio_put_pm(struct msm_dp_audio_private *audio) +{ + int ret; + + /* An idle refusal still consumes this temporary parent-PM lease. */ + ret = pm_runtime_put_sync(&audio->pdev->dev); + if (ret < 0) + dev_dbg(&audio->pdev->dev, "audio runtime idle refused: %d\n", ret); +} + int msm_dp_audio_prepare(struct drm_bridge *bridge, struct drm_connector *connector, struct hdmi_codec_daifmt *daifmt, struct hdmi_codec_params *params) { int rc = 0; + int active; struct msm_dp_audio_private *audio; struct msm_dp *msm_dp_display; msm_dp_display = to_dp_bridge(bridge)->msm_dp_display; - /* - * Userspace can probe DP PCMs while the connector is unplugged or - * disabled. The controller clocks are off then, so skip register - * programming; jack and ELD state still report sink availability. - */ - if (!msm_dp_display->power_on) - return 0; - audio = msm_dp_audio_get_data(msm_dp_display); - if (IS_ERR(audio)) { - rc = PTR_ERR(audio); - goto end; + if (IS_ERR(audio)) + return PTR_ERR(audio); + + /* The HDMI codec advertises two to eight playback channels. */ + if (params->channels < 2 || params->channels > 8) + return -EINVAL; + + /* Parent runtime callbacks take the same state lock. */ + active = pm_runtime_get_if_active(&audio->pdev->dev); + if (active < 0) + return active; + + mutex_lock(audio->state_lock); + /* Software intent can be staged while inactive, without waking the HW. */ + if (!msm_dp_display->power_on) { + audio->channels = params->channels; + audio->prepared = true; + goto unlock; + } + if (!active) { + rc = -EAGAIN; + goto unlock; + } + if (!msm_dp_audio_ready(audio, msm_dp_display)) { + rc = -EIO; + goto unlock; } audio->channels = params->channels; - msm_dp_audio_setup_sdp(audio); - msm_dp_audio_setup_acr(audio); - msm_dp_audio_safe_to_exit_level(audio); - msm_dp_audio_enable(audio, true); - msm_dp_display_signal_audio_start(msm_dp_display); + msm_dp_audio_program(audio); msm_dp_display->audio_enabled = true; + audio->prepared = true; -end: +unlock: + mutex_unlock(audio->state_lock); + if (active > 0) + msm_dp_audio_put_pm(audio); return rc; } @@ -308,6 +397,7 @@ void msm_dp_audio_shutdown(struct drm_bridge *bridge, { struct msm_dp_audio_private *audio; struct msm_dp *msm_dp_display; + int active, ret = 0; msm_dp_display = to_dp_bridge(bridge)->msm_dp_display; audio = msm_dp_audio_get_data(msm_dp_display); @@ -316,30 +406,36 @@ void msm_dp_audio_shutdown(struct drm_bridge *bridge, return; } - /* - * if audio was not enabled there is no need - * to execute the shutdown and we can bail out early. - * This also makes sure that we dont cause an unclocked - * access when audio subsystem calls this without DP being - * connected. is_connected cannot be used here as its set - * to false earlier than this call - */ - if (!msm_dp_display->audio_enabled) - return; - - msm_dp_audio_enable(audio, false); - /* signal the dp display to safely shutdown clocks */ - msm_dp_display_signal_audio_complete(msm_dp_display); + /* Late ALSA shutdown must not resume a disconnected display. */ + active = pm_runtime_get_if_active(&audio->pdev->dev); + mutex_lock(audio->state_lock); + /* Clearing ALSA intent is software-only, not a hardware-stop claim. */ + audio->prepared = false; + if (active > 0) + ret = msm_dp_audio_stop(msm_dp_display); + else if (msm_dp_display->audio_enabled) + ret = active < 0 ? active : -EAGAIN; + mutex_unlock(audio->state_lock); + if (active < 0) + dev_err(&audio->pdev->dev, + "audio shutdown PM admission failed: %d\n", active); + if (ret) + dev_err(&audio->pdev->dev, + "audio shutdown could not disable hardware: %d\n", ret); + if (active > 0) + msm_dp_audio_put_pm(audio); } struct msm_dp_audio *msm_dp_audio_get(struct platform_device *pdev, - void __iomem *link_base) + void __iomem *link_base, + struct msm_dp_ctrl *ctrl, + struct mutex *state_lock) { int rc = 0; struct msm_dp_audio_private *audio; struct msm_dp_audio *msm_dp_audio; - if (!pdev) { + if (!pdev || !ctrl || !state_lock) { DRM_ERROR("invalid input\n"); rc = -EINVAL; goto error; @@ -353,6 +449,8 @@ struct msm_dp_audio *msm_dp_audio_get(struct platform_device *pdev, audio->pdev = pdev; audio->link_base = link_base; + audio->ctrl = ctrl; + audio->state_lock = state_lock; msm_dp_audio = &audio->msm_dp_audio; diff --git a/drivers/gpu/drm/msm/dp/dp_audio.h b/drivers/gpu/drm/msm/dp/dp_audio.h index ce2342856adb10..9cffcfd80283fa 100644 --- a/drivers/gpu/drm/msm/dp/dp_audio.h +++ b/drivers/gpu/drm/msm/dp/dp_audio.h @@ -11,6 +11,9 @@ #include struct drm_bridge; +struct msm_dp; +struct msm_dp_ctrl; +struct mutex; /** * struct msm_dp_audio @@ -29,12 +32,26 @@ struct msm_dp_audio { * * @pdev: caller's platform device instance. * @link_base: pointer to the msm_dp_link resource. + * @ctrl: display controller sharing the audio transition lock. + * @state_lock: display-owned lock, acquired only after a parent-PM lease. * * Returns the error code in case of failure, otherwize * an instance of newly created msm_dp_module. */ struct msm_dp_audio *msm_dp_audio_get(struct platform_device *pdev, - void __iomem *link_base); + void __iomem *link_base, + struct msm_dp_ctrl *ctrl, + struct mutex *state_lock); + +/* + * Caller holds state_lock and an active parent-PM lease. Stop audio before + * controller/clock teardown; late ALSA shutdown is then a logical no-op. + * A nonzero result retains audio_enabled: hardware stop was not confirmed. + */ +int msm_dp_audio_stop(struct msm_dp *msm_dp_display); + +/* Same lock/lease contract; restore only current, accepted ALSA prepare intent. */ +int msm_dp_audio_restart(struct msm_dp *msm_dp_display); /** * msm_dp_audio_put() diff --git a/drivers/gpu/drm/msm/dp/dp_aux.c b/drivers/gpu/drm/msm/dp/dp_aux.c index 3825a2fb48e213..d61e6db00abaa3 100644 --- a/drivers/gpu/drm/msm/dp/dp_aux.c +++ b/drivers/gpu/drm/msm/dp/dp_aux.c @@ -3,9 +3,11 @@ * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. */ +#include #include #include #include +#include #include #include "dp_reg.h" @@ -40,12 +42,44 @@ struct msm_dp_aux_private { bool initted; bool is_edp; bool enable_xfers; + /* All owner/fault fields below are protected by mutex. */ + int phy_fault; + bool fault_pm_held; + bool owner_stopping; + const struct msm_dp_aux_owner_ops *owner_ops; + void *owner_data; + unsigned int active_admissions; + struct completion owner_idle; + struct work_struct fault_work; u32 offset; u32 segment; struct drm_dp_aux msm_dp_aux; }; +static void msm_dp_aux_fault_work(struct work_struct *work) +{ + struct msm_dp_aux_private *aux = container_of(work, + struct msm_dp_aux_private, fault_work); + const struct msm_dp_aux_owner_ops *ops; + void *data; + + mutex_lock(&aux->mutex); + if (!aux->fault_pm_held) { + mutex_unlock(&aux->mutex); + return; + } + ops = aux->owner_ops; + data = aux->owner_data; + mutex_unlock(&aux->mutex); + + /* Stop retains the callback until this work has been flushed. */ + if (WARN_ON_ONCE(!ops || !ops->recover)) + return; + ops->recover(data); + /* The owner finished/adopted/put the lease; do not clear flags here. */ +} + static inline u32 msm_dp_read_aux(struct msm_dp_aux_private *aux, u32 offset) { return readl_relaxed(aux->aux_base + offset); @@ -108,16 +142,33 @@ static void msm_dp_aux_disable(struct msm_dp_aux_private *aux) msm_dp_write_aux(aux, REG_DP_AUX_CTRL, aux_ctrl); } +static int msm_dp_aux_read_hpd_state(struct msm_dp_aux_private *aux) +{ + int state; + + /* Each core-only read must finish before aux_deinit allows clock-off. */ + mutex_lock(&aux->mutex); + /* Core-only panel HPD remains valid across a temporary DRM unbind. */ + if (!aux->initted) + state = -EIO; + else + state = !!(msm_dp_read_aux(aux, REG_DP_DP_HPD_INT_STATUS) & + DP_DP_HPD_STATE_STATUS_CONNECTED); + mutex_unlock(&aux->mutex); + return state; +} + static int msm_dp_aux_wait_for_hpd_connect_state(struct msm_dp_aux_private *aux, unsigned long wait_us) { - u32 state; + int state, ret; - /* poll for hpd connected status every 2ms and timeout after wait_us */ - return readl_poll_timeout(aux->aux_base + - REG_DP_DP_HPD_INT_STATUS, - state, state & DP_DP_HPD_STATE_STATUS_CONNECTED, - min(wait_us, 2000), wait_us); + /* No state/aux mutex is held across the sleep or the whole panel wait. */ + ret = read_poll_timeout(msm_dp_aux_read_hpd_state, state, state != 0, + min(wait_us, 2000), wait_us, false, aux); + if (ret) + return ret; + return state < 0 ? state : 0; } #define MAX_AUX_RETRIES 5 @@ -346,6 +397,9 @@ static ssize_t msm_dp_aux_transfer(struct drm_dp_aux *msm_dp_aux, struct drm_dp_aux_msg *msg) { ssize_t ret; + int calibration_ret; + + bool put_temporary = true; int const aux_cmd_native_max = 16; int const aux_cmd_i2c_max = 128; struct msm_dp_aux_private *aux; @@ -374,22 +428,65 @@ static ssize_t msm_dp_aux_transfer(struct drm_dp_aux *msm_dp_aux, return ret; mutex_lock(&aux->mutex); + if (aux->owner_stopping) { + ret = -ENODEV; + goto exit; + } if (!aux->initted) { ret = -EIO; goto exit; } - /* - * If we're using DP and an external display isn't connected then the - * transfer won't succeed. Return right away. If we don't do this we - * can end up with long timeouts if someone tries to access the DP AUX - * character device when no DP device is connected. - */ - if (!aux->is_edp && !aux->enable_xfers) { - ret = -ENXIO; + /* A completed worker may leave a retained fault for checked retry. */ + if (aux->phy_fault && aux->fault_pm_held) { + ret = aux->phy_fault; goto exit; } + /* Both DP and eDP need the display owner to admit PHY-backed transfers. */ + if (!aux->enable_xfers) { + const struct msm_dp_aux_owner_ops *ops = aux->owner_ops; + void *data = aux->owner_data; + + if (!ops) { + ret = -ENXIO; + goto exit; + } + if (!aux->active_admissions) + reinit_completion(&aux->owner_idle); + aux->active_admissions++; + mutex_unlock(&aux->mutex); + /* The owner may only trylock state: hw_mutex can be held here. */ + ret = ops->admit(data, &put_temporary); + mutex_lock(&aux->mutex); + aux->active_admissions--; + if (!aux->active_admissions) + complete_all(&aux->owner_idle); + if (WARN_ON_ONCE(ret > 0 || (!ret && !put_temporary))) { + aux->enable_xfers = false; + ret = -EIO; + } + if (ret) + goto exit; + /* Disconnect/stop/fault may have won while aux.mutex was dropped. */ + if (aux->owner_stopping) { + ret = -ENODEV; + goto exit; + } + if (!aux->initted) { + ret = -EIO; + goto exit; + } + if (aux->phy_fault) { + ret = aux->phy_fault; + goto exit; + } + if (!aux->enable_xfers) { + ret = -ENXIO; + goto exit; + } + } + msm_dp_aux_update_offset_and_segment(aux, msg); msm_dp_aux_transfer_helper(aux, msg, true); @@ -408,8 +505,24 @@ static ssize_t msm_dp_aux_transfer(struct drm_dp_aux *msm_dp_aux, if (ret < 0) { if (aux->native) { aux->retry_cnt++; - if (!(aux->retry_cnt % MAX_AUX_RETRIES)) - phy_calibrate(aux->phy); + if (!(aux->retry_cnt % MAX_AUX_RETRIES)) { + calibration_ret = phy_calibrate(aux->phy); + if (calibration_ret < 0) { + DRM_ERROR("PHY calibration failed after AUX error %zd: %d\n", + ret, calibration_ret); + ret = calibration_ret; + if (!aux->phy_fault) + aux->phy_fault = calibration_ret; + aux->enable_xfers = false; + if (!aux->fault_pm_held) { + aux->fault_pm_held = true; + put_temporary = false; + /* Queue before stop can finish draining mutex. */ + schedule_work(&aux->fault_work); + } + goto clear_busy; + } + } } /* reset aux if link is in connected state */ if (msm_dp_aux_is_link_connected(msm_dp_aux)) @@ -437,11 +550,13 @@ static ssize_t msm_dp_aux_transfer(struct drm_dp_aux *msm_dp_aux, } } +clear_busy: aux->cmd_busy = false; exit: mutex_unlock(&aux->mutex); - pm_runtime_put_sync(msm_dp_aux->dev); + if (put_temporary) + pm_runtime_put_sync(msm_dp_aux->dev); return ret; } @@ -499,12 +614,143 @@ irqreturn_t msm_dp_aux_isr(struct drm_dp_aux *msm_dp_aux, u32 isr) return IRQ_HANDLED; } -void msm_dp_aux_enable_xfers(struct drm_dp_aux *msm_dp_aux, bool enabled) +bool msm_dp_aux_enable_xfers(struct drm_dp_aux *msm_dp_aux, bool enabled) { struct msm_dp_aux_private *aux; + bool was_enabled; aux = container_of(msm_dp_aux, struct msm_dp_aux_private, msm_dp_aux); - aux->enable_xfers = enabled; + /* Closing the gate also drains a normal transfer holding this mutex. */ + mutex_lock(&aux->mutex); + was_enabled = aux->enable_xfers; + aux->enable_xfers = enabled && !aux->phy_fault && !aux->owner_stopping; + mutex_unlock(&aux->mutex); + + return was_enabled; +} + +int msm_dp_aux_admit_xfers(struct drm_dp_aux *msm_dp_aux) +{ + struct msm_dp_aux_private *aux = container_of(msm_dp_aux, + struct msm_dp_aux_private, msm_dp_aux); + int ret = 0; + + mutex_lock(&aux->mutex); + if (aux->owner_stopping) + ret = -ENODEV; + else if (aux->phy_fault) + ret = aux->phy_fault; + else if (!aux->initted) + ret = -EIO; + else + aux->enable_xfers = true; + mutex_unlock(&aux->mutex); + return ret; +} + +int msm_dp_aux_get_fault(struct drm_dp_aux *msm_dp_aux) +{ + struct msm_dp_aux_private *aux = container_of(msm_dp_aux, + struct msm_dp_aux_private, msm_dp_aux); + int fault; + + mutex_lock(&aux->mutex); + fault = aux->phy_fault; + mutex_unlock(&aux->mutex); + return fault; +} + +bool msm_dp_aux_fault_pending(struct drm_dp_aux *msm_dp_aux) +{ + struct msm_dp_aux_private *aux = container_of(msm_dp_aux, + struct msm_dp_aux_private, msm_dp_aux); + bool pending; + + mutex_lock(&aux->mutex); + pending = aux->fault_pm_held; + mutex_unlock(&aux->mutex); + return pending; +} + +bool msm_dp_aux_finish_fault(struct drm_dp_aux *msm_dp_aux, bool fully_drained) +{ + struct msm_dp_aux_private *aux = container_of(msm_dp_aux, + struct msm_dp_aux_private, msm_dp_aux); + bool held; + + /* Caller serializes recovery/admission with display state_lock. */ + mutex_lock(&aux->mutex); + held = aux->fault_pm_held; + aux->fault_pm_held = false; + aux->enable_xfers = false; + if (fully_drained) { + aux->phy_fault = 0; + aux->retry_cnt = 0; + } + mutex_unlock(&aux->mutex); + return held; +} + +int msm_dp_aux_owner_set(struct drm_dp_aux *msm_dp_aux, + const struct msm_dp_aux_owner_ops *ops, void *data) +{ + struct msm_dp_aux_private *aux = container_of(msm_dp_aux, + struct msm_dp_aux_private, msm_dp_aux); + int ret = 0; + + if (!ops || !ops->admit || !ops->recover || !data) + return -EINVAL; + mutex_lock(&aux->mutex); + if (aux->active_admissions || aux->phy_fault || aux->fault_pm_held || + (aux->owner_ops && (!aux->owner_stopping || aux->owner_ops != ops || + aux->owner_data != data))) + ret = -EBUSY; + else { + aux->owner_ops = ops; + aux->owner_data = data; + aux->owner_stopping = false; + } + mutex_unlock(&aux->mutex); + return ret; +} + +void msm_dp_aux_owner_stop(struct drm_dp_aux *msm_dp_aux) +{ + struct msm_dp_aux_private *aux = container_of(msm_dp_aux, + struct msm_dp_aux_private, msm_dp_aux); + + mutex_lock(&aux->mutex); + aux->owner_stopping = true; + aux->enable_xfers = false; + mutex_unlock(&aux->mutex); +} + +void msm_dp_aux_owner_drain(struct drm_dp_aux *msm_dp_aux) +{ + struct msm_dp_aux_private *aux = container_of(msm_dp_aux, + struct msm_dp_aux_private, msm_dp_aux); + + /* owner_stop forbids new admissions/reports before either wait. */ + wait_for_completion(&aux->owner_idle); + flush_work(&aux->fault_work); +} + +int msm_dp_aux_owner_detach(struct drm_dp_aux *msm_dp_aux) +{ + struct msm_dp_aux_private *aux = container_of(msm_dp_aux, + struct msm_dp_aux_private, msm_dp_aux); + int ret = 0; + + mutex_lock(&aux->mutex); + if (!aux->owner_stopping || aux->active_admissions || aux->fault_pm_held || + aux->phy_fault) + ret = -EBUSY; + else { + aux->owner_ops = NULL; + aux->owner_data = NULL; + } + mutex_unlock(&aux->mutex); + return ret; } void msm_dp_aux_reconfig(struct drm_dp_aux *msm_dp_aux) @@ -691,6 +937,11 @@ struct drm_dp_aux *msm_dp_aux_get(struct device *dev, return ERR_PTR(-ENOMEM); init_completion(&aux->comp); + init_completion(&aux->owner_idle); + complete_all(&aux->owner_idle); + INIT_WORK(&aux->fault_work, msm_dp_aux_fault_work); + /* No raw PHY-backed transaction before the complete owner attaches. */ + aux->owner_stopping = true; aux->cmd_busy = false; aux->is_edp = is_edp; mutex_init(&aux->mutex); diff --git a/drivers/gpu/drm/msm/dp/dp_aux.h b/drivers/gpu/drm/msm/dp/dp_aux.h index 4be02e8b4d0bae..9fe9d4600d82b0 100644 --- a/drivers/gpu/drm/msm/dp/dp_aux.h +++ b/drivers/gpu/drm/msm/dp/dp_aux.h @@ -8,10 +8,39 @@ #include +/* + * admit runs with the raw caller's parent-PM lease, but without aux.mutex. + * Its caller may hold drm_dp_aux.hw_mutex: never block on display state_lock. + * On an error only, it may transfer the lease to cleanup accounting by + * clearing put_temporary. Success must leave that lease with the raw caller. + * recover owns the first fault's transferred lease. It must finish_fault + * while holding display state_lock, adopt the returned lease under that lock + * or put it after unlocking. It must account for the lease while stopping. + */ +struct msm_dp_aux_owner_ops { + int (*admit)(void *data, bool *put_temporary); + void (*recover)(void *data); +}; + +/* First attach, or restart the identical owner only after stop and drain. */ +int msm_dp_aux_owner_set(struct drm_dp_aux *aux, + const struct msm_dp_aux_owner_ops *ops, void *data); +/* Stop under display state_lock; drain outside state/aux/hw_mutex. */ +void msm_dp_aux_owner_stop(struct drm_dp_aux *aux); +void msm_dp_aux_owner_drain(struct drm_dp_aux *aux); +/* Detach only after owner drain and AUX child/interface/raw-user drains. */ +int msm_dp_aux_owner_detach(struct drm_dp_aux *aux); +int msm_dp_aux_get_fault(struct drm_dp_aux *aux); +bool msm_dp_aux_fault_pending(struct drm_dp_aux *aux); +/* Caller holds display state_lock; true transfers one actual parent lease. */ +bool msm_dp_aux_finish_fault(struct drm_dp_aux *aux, bool fully_drained); +/* Checked gate publication after actual PHY admission, under state_lock. */ +int msm_dp_aux_admit_xfers(struct drm_dp_aux *aux); + int msm_dp_aux_register(struct drm_dp_aux *msm_dp_aux); void msm_dp_aux_unregister(struct drm_dp_aux *msm_dp_aux); irqreturn_t msm_dp_aux_isr(struct drm_dp_aux *msm_dp_aux, u32 isr); -void msm_dp_aux_enable_xfers(struct drm_dp_aux *msm_dp_aux, bool enabled); +bool msm_dp_aux_enable_xfers(struct drm_dp_aux *msm_dp_aux, bool enabled); void msm_dp_aux_init(struct drm_dp_aux *msm_dp_aux); void msm_dp_aux_deinit(struct drm_dp_aux *msm_dp_aux); void msm_dp_aux_reconfig(struct drm_dp_aux *msm_dp_aux); diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index df4a6af873dae7..f6465774f4a84b 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -115,11 +116,20 @@ struct msm_dp_ctrl_private { struct drm_device *drm_dev; struct device *dev; struct drm_dp_aux *aux; + struct msm_dp_panel *panel; struct msm_dp_link *link; void __iomem *ahb_base; void __iomem *link_base; struct phy *phy; + /* Shared display/controller transition lock; never acquired by the ISR. */ + struct mutex *state_lock; + + /* One successful generic-PHY reference of each kind, not hardware status. */ + bool phy_init_owned; + bool phy_power_owned; + int phy_error; + int cleanup_error; unsigned int num_core_clks; struct clk_bulk_data *core_clks; @@ -143,6 +153,108 @@ struct msm_dp_ctrl_private { bool clk_defaults_set; }; +static int msm_dp_ctrl_stop_link(struct msm_dp_ctrl_private *ctrl, bool reset); + +static void msm_dp_ctrl_clear_drained_phy_error(struct msm_dp_ctrl_private *ctrl) +{ + lockdep_assert_held(ctrl->state_lock); + if (!ctrl->phy_init_owned && !ctrl->phy_power_owned) { + ctrl->phy_error = 0; + ctrl->cleanup_error = 0; + } +} + +static int msm_dp_ctrl_phy_failed(struct msm_dp_ctrl_private *ctrl, int error, + bool cleanup) +{ + lockdep_assert_held(ctrl->state_lock); + msm_dp_aux_enable_xfers(ctrl->aux, false); + if (!ctrl->phy_error) + ctrl->phy_error = error; + if (cleanup && !ctrl->cleanup_error) + ctrl->cleanup_error = error; + return error; +} + +static int msm_dp_ctrl_phy_status(struct msm_dp_ctrl_private *ctrl) +{ + int ret; + + lockdep_assert_held(ctrl->state_lock); + ret = msm_dp_aux_get_fault(ctrl->aux); + if (ret) + return msm_dp_ctrl_phy_failed(ctrl, ret, false); + return ctrl->phy_error; +} + +static int msm_dp_ctrl_restore_aux(struct msm_dp_ctrl_private *ctrl, bool xfers) +{ + int ret; + + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret || !xfers) + return ret; + ret = msm_dp_aux_admit_xfers(ctrl->aux); + if (ret) + return msm_dp_ctrl_phy_failed(ctrl, ret, false); + return 0; +} + +static int msm_dp_ctrl_configure_phy(struct msm_dp_ctrl_private *ctrl) +{ + bool xfers; + int ret; + + lockdep_assert_held(ctrl->state_lock); + xfers = msm_dp_aux_enable_xfers(ctrl->aux, false); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; + if (!ctrl->phy_init_owned) + return -EINVAL; + ret = phy_configure(ctrl->phy, &ctrl->phy_opts); + if (ret) + return msm_dp_ctrl_phy_failed(ctrl, ret, false); + return msm_dp_ctrl_restore_aux(ctrl, xfers); +} + +static int msm_dp_ctrl_phy_power_get(struct msm_dp_ctrl_private *ctrl) +{ + bool xfers; + int ret; + + lockdep_assert_held(ctrl->state_lock); + xfers = msm_dp_aux_enable_xfers(ctrl->aux, false); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; + if (!ctrl->phy_init_owned) + return -EINVAL; + if (!ctrl->phy_power_owned) { + ret = phy_power_on(ctrl->phy); + if (ret) + return msm_dp_ctrl_phy_failed(ctrl, ret, false); + ctrl->phy_power_owned = true; + } + return msm_dp_ctrl_restore_aux(ctrl, xfers); +} + +static int msm_dp_ctrl_phy_power_put(struct msm_dp_ctrl_private *ctrl) +{ + int ret; + + lockdep_assert_held(ctrl->state_lock); + msm_dp_aux_enable_xfers(ctrl->aux, false); + if (!ctrl->phy_power_owned) + return 0; + ret = phy_power_off(ctrl->phy); + if (ret) + return msm_dp_ctrl_phy_failed(ctrl, ret, true); + ctrl->phy_power_owned = false; + msm_dp_ctrl_clear_drained_phy_error(ctrl); + return 0; +} + static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset) { return readl_relaxed(ctrl->ahb_base + offset); @@ -203,13 +315,13 @@ static int msm_dp_aux_link_configure(struct drm_dp_aux *aux, /* * NOTE: resetting DP controller will also clear any pending HPD related interrupts */ -void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel) +void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); u32 sw_reset; + lockdep_assert_held(ctrl->state_lock); sw_reset = msm_dp_read_ahb(ctrl, REG_DP_SW_RESET); sw_reset |= DP_SW_RESET; @@ -219,9 +331,10 @@ void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl, sw_reset &= ~DP_SW_RESET; msm_dp_write_ahb(ctrl, REG_DP_SW_RESET, sw_reset); - if (!ctrl->hw_revision) + if (!ctrl->hw_revision) { ctrl->hw_revision = msm_dp_read_ahb(ctrl, REG_DP_HW_VERSION); - panel->hw_revision = ctrl->hw_revision; + ctrl->panel->hw_revision = ctrl->hw_revision; + } } static u32 msm_dp_ctrl_get_aux_interrupt(struct msm_dp_ctrl_private *ctrl) @@ -258,6 +371,7 @@ void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl) struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + lockdep_assert_held(ctrl->state_lock); msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS, DP_INTERRUPT_STATUS1_MASK); msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2, @@ -269,6 +383,7 @@ void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl) struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + lockdep_assert_held(ctrl->state_lock); msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS, 0x00); msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2, 0x00); } @@ -361,6 +476,9 @@ static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl) u32 data; int ret; + if (msm_dp_ctrl_phy_status(ctrl)) + return false; + /* Poll for mainlink ready status */ ret = readl_poll_timeout(ctrl->link_base + REG_DP_MAINLINK_READY, data, data & DP_MAINLINK_READY_FOR_VIDEO, @@ -370,7 +488,7 @@ static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl) return false; } - return true; + return !msm_dp_ctrl_phy_status(ctrl); } void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl) @@ -379,6 +497,9 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + lockdep_assert_held(ctrl->state_lock); + if (!ctrl->link_clks_on) + return; reinit_completion(&ctrl->idle_comp); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_PUSH_IDLE); @@ -389,49 +510,35 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl) drm_dbg_dp(ctrl->drm_dev, "mainlink off\n"); } -static void msm_dp_ctrl_config_ctrl_streams(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *msm_dp_panel) +static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl) { u32 config = 0, tbd; + const u8 *dpcd = ctrl->panel->dpcd; + + /* Default-> LSCLK DIV: 1/4 LCLK */ + config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT); /* - * RMW: called from atomic_enable(), serialized by the DRM atomic framework. - * Clear stream-specific fields before OR-ing to avoid bit accumulation. + * RMW: serialized by the DRM atomic framework. Clear stream-specific + * fields before OR-ing to avoid bit accumulation across re-enables. */ config = msm_dp_read_link(ctrl, REG_DP_CONFIGURATION_CTRL); config &= ~(DP_CONFIGURATION_CTRL_RGB_YUV_MASK | DP_CONFIGURATION_CTRL_BPC_MASK | DP_CONFIGURATION_CTRL_SEND_VSC); - if (msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420) + if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */ - tbd = msm_dp_link_get_test_bits_depth(ctrl->link, - msm_dp_panel->msm_dp_mode.bpp); - - config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT; - - if (msm_dp_panel->psr_cap.version) - config |= DP_CONFIGURATION_CTRL_SEND_VSC; - - drm_dbg_dp(ctrl->drm_dev, "stream DP_CONFIGURATION_CTRL=0x%x\n", config); - - msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config); -} - -static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel) -{ - u32 config = 0; - const u8 *dpcd = panel->dpcd; - - /* Default-> LSCLK DIV: 1/4 LCLK */ - config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT); - /* Scrambler reset enable */ if (drm_dp_alternate_scrambler_reset_cap(dpcd)) config |= DP_CONFIGURATION_CTRL_ASSR; + tbd = msm_dp_link_get_test_bits_depth(ctrl->link, + ctrl->panel->msm_dp_mode.bpp); + + config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT; + /* Num of Lanes */ config |= ((ctrl->link->link_params.num_lanes - 1) << DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT); @@ -445,7 +552,10 @@ static void msm_dp_ctrl_config_ctrl_link(struct msm_dp_ctrl_private *ctrl, config |= DP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN; config |= DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK; - drm_dbg_dp(ctrl->drm_dev, "link DP_CONFIGURATION_CTRL=0x%x\n", config); + if (ctrl->panel->psr_cap.version) + config |= DP_CONFIGURATION_CTRL_SEND_VSC; + + drm_dbg_dp(ctrl->drm_dev, "DP_CONFIGURATION_CTRL=0x%x\n", config); msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config); } @@ -464,13 +574,16 @@ static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl) ln_mapping); } -static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *msm_dp_panel) +static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl) { u32 colorimetry_cfg, test_bits_depth, misc_val; - test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, - msm_dp_panel->msm_dp_mode.bpp); + msm_dp_ctrl_lane_mapping(ctrl); + msm_dp_setup_peripheral_flush(ctrl); + + msm_dp_ctrl_config_ctrl(ctrl); + + test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, ctrl->panel->msm_dp_mode.bpp); colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link); misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0); @@ -484,16 +597,8 @@ static void msm_dp_ctrl_config_misc1_misc0(struct msm_dp_ctrl_private *ctrl, drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val); msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val); -} - -static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel) -{ - msm_dp_ctrl_config_ctrl_streams(ctrl, panel); - msm_dp_ctrl_config_misc1_misc0(ctrl, panel); - - msm_dp_panel_timing_cfg(panel, ctrl->msm_dp_ctrl.wide_bus_en); + msm_dp_panel_timing_cfg(ctrl->panel, ctrl->msm_dp_ctrl.wide_bus_en); } /* @@ -1263,21 +1368,20 @@ static void _dp_ctrl_calc_tu(struct msm_dp_ctrl_private *ctrl, } static void msm_dp_ctrl_calc_tu_parameters(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel, struct msm_dp_vc_tu_mapping_table *tu_table) { struct msm_dp_tu_calc_input in; - const struct drm_display_mode *drm_mode; + struct drm_display_mode *drm_mode; - drm_mode = &panel->msm_dp_mode.drm_mode; + drm_mode = &ctrl->panel->msm_dp_mode.drm_mode; in.lclk = ctrl->link->link_params.rate / 1000; in.pclk_khz = drm_mode->clock; in.hactive = drm_mode->hdisplay; in.hporch = drm_mode->htotal - drm_mode->hdisplay; in.nlanes = ctrl->link->link_params.num_lanes; - in.bpp = panel->msm_dp_mode.bpp; - in.pixel_enc = panel->msm_dp_mode.out_fmt_is_yuv_420 ? 420 : 444; + in.bpp = ctrl->panel->msm_dp_mode.bpp; + in.pixel_enc = ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420 ? 420 : 444; in.dsc_en = 0; in.async_en = 0; in.fec_en = 0; @@ -1287,15 +1391,14 @@ static void msm_dp_ctrl_calc_tu_parameters(struct msm_dp_ctrl_private *ctrl, _dp_ctrl_calc_tu(ctrl, &in, tu_table); } -static void msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel) +static void msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl) { u32 msm_dp_tu = 0x0; u32 valid_boundary = 0x0; u32 valid_boundary2 = 0x0; struct msm_dp_vc_tu_mapping_table tu_calc_table; - msm_dp_ctrl_calc_tu_parameters(ctrl, panel, &tu_calc_table); + msm_dp_ctrl_calc_tu_parameters(ctrl, &tu_calc_table); msm_dp_tu |= tu_calc_table.tu_size_minus1; valid_boundary |= tu_calc_table.valid_boundary_link; @@ -1318,29 +1421,35 @@ static void msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl, static int msm_dp_ctrl_wait4video_ready(struct msm_dp_ctrl_private *ctrl) { - int ret = 0; + int ret; + + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; if (!wait_for_completion_timeout(&ctrl->video_comp, WAIT_FOR_VIDEO_READY_TIMEOUT_JIFFIES)) { DRM_ERROR("wait4video timedout\n"); ret = -ETIMEDOUT; } - return ret; + return msm_dp_ctrl_phy_status(ctrl) ?: ret; } static int msm_dp_ctrl_set_vx_px(struct msm_dp_ctrl_private *ctrl, u8 v_level, u8 p_level) { union phy_configure_opts *phy_opts = &ctrl->phy_opts; + int ret; /* TODO: Update for all lanes instead of just first one */ phy_opts->dp.voltage[0] = v_level; phy_opts->dp.pre[0] = p_level; phy_opts->dp.set_voltages = 1; - phy_configure(ctrl->phy, phy_opts); + ret = msm_dp_ctrl_configure_phy(ctrl); + phy_opts->dp.set_voltages = 0; - return 0; + return ret; } static int msm_dp_ctrl_update_phy_vx_px(struct msm_dp_ctrl_private *ctrl, @@ -1396,7 +1505,7 @@ static int msm_dp_ctrl_update_phy_vx_px(struct msm_dp_ctrl_private *ctrl, if (ret == lane_cnt) ret = 0; - return ret; + return msm_dp_ctrl_phy_status(ctrl) ?: ret; } static bool msm_dp_ctrl_train_pattern_set(struct msm_dp_ctrl_private *ctrl, @@ -1428,6 +1537,10 @@ static int msm_dp_ctrl_set_pattern_state_bit(struct msm_dp_ctrl_private *ctrl, int bit, ret; u32 data; + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; + bit = BIT(state_bit - 1); drm_dbg_dp(ctrl->drm_dev, "hw: bit=%d train=%d\n", bit, state_bit); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, bit); @@ -1447,7 +1560,6 @@ static int msm_dp_ctrl_set_pattern_state_bit(struct msm_dp_ctrl_private *ctrl, } static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel, int *training_step, enum drm_dp_phy dp_phy) { int delay_us; @@ -1456,7 +1568,10 @@ static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl, int const maximum_retries = 4; delay_us = drm_dp_read_clock_recovery_delay(ctrl->aux, - panel->dpcd, dp_phy, false); + ctrl->panel->dpcd, dp_phy, false); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0); @@ -1467,6 +1582,9 @@ static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl, return ret; msm_dp_ctrl_train_pattern_set(ctrl, DP_TRAINING_PATTERN_1 | DP_LINK_SCRAMBLING_DISABLE, dp_phy); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; msm_dp_link_reset_phy_params_vx_px(ctrl->link); ret = msm_dp_ctrl_update_phy_vx_px(ctrl, dp_phy); @@ -1479,6 +1597,9 @@ static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl, fsleep(delay_us); ret = drm_dp_dpcd_read_phy_link_status(ctrl->aux, dp_phy, link_status); + if (ret) + return ret; + ret = msm_dp_ctrl_phy_status(ctrl); if (ret) return ret; @@ -1542,15 +1663,14 @@ static int msm_dp_ctrl_link_rate_down_shift(struct msm_dp_ctrl_private *ctrl) return ret; } -static int msm_dp_ctrl_link_lane_down_shift(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel) +static int msm_dp_ctrl_link_lane_down_shift(struct msm_dp_ctrl_private *ctrl) { if (ctrl->link->link_params.num_lanes == 1) return -1; ctrl->link->link_params.num_lanes /= 2; - ctrl->link->link_params.rate = panel->link_info.rate; + ctrl->link->link_params.rate = ctrl->panel->link_info.rate; ctrl->link->phy_params.p_level = 0; ctrl->link->phy_params.v_level = 0; @@ -1559,20 +1679,25 @@ static int msm_dp_ctrl_link_lane_down_shift(struct msm_dp_ctrl_private *ctrl, } static void msm_dp_ctrl_clear_training_pattern(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel, enum drm_dp_phy dp_phy) { int delay_us; + if (msm_dp_ctrl_phy_status(ctrl)) + return; + msm_dp_ctrl_train_pattern_set(ctrl, DP_TRAINING_PATTERN_DISABLE, dp_phy); + if (msm_dp_ctrl_phy_status(ctrl)) + return; delay_us = drm_dp_read_channel_eq_delay(ctrl->aux, - panel->dpcd, dp_phy, false); + ctrl->panel->dpcd, dp_phy, false); + if (msm_dp_ctrl_phy_status(ctrl)) + return; fsleep(delay_us); } static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel, int *training_step, enum drm_dp_phy dp_phy) { int delay_us; @@ -1583,16 +1708,19 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl, u8 link_status[DP_LINK_STATUS_SIZE]; delay_us = drm_dp_read_channel_eq_delay(ctrl->aux, - panel->dpcd, dp_phy, false); + ctrl->panel->dpcd, dp_phy, false); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0); *training_step = DP_TRAINING_2; - if (drm_dp_tps4_supported(panel->dpcd)) { + if (drm_dp_tps4_supported(ctrl->panel->dpcd)) { pattern = DP_TRAINING_PATTERN_4; state_ctrl_bit = 4; - } else if (drm_dp_tps3_supported(panel->dpcd)) { + } else if (drm_dp_tps3_supported(ctrl->panel->dpcd)) { pattern = DP_TRAINING_PATTERN_3; state_ctrl_bit = 3; } else { @@ -1605,11 +1733,17 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl, return ret; msm_dp_ctrl_train_pattern_set(ctrl, pattern, dp_phy); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; for (tries = 0; tries <= maximum_retries; tries++) { fsleep(delay_us); ret = drm_dp_dpcd_read_phy_link_status(ctrl->aux, dp_phy, link_status); + if (ret) + return ret; + ret = msm_dp_ctrl_phy_status(ctrl); if (ret) return ret; @@ -1629,19 +1763,18 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl, } static int msm_dp_ctrl_link_train_1_2(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel, int *training_step, enum drm_dp_phy dp_phy) { int ret; - ret = msm_dp_ctrl_link_train_1(ctrl, panel, training_step, dp_phy); + ret = msm_dp_ctrl_link_train_1(ctrl, training_step, dp_phy); if (ret) { DRM_ERROR("link training #1 on phy %d failed. ret=%d\n", dp_phy, ret); return ret; } drm_dbg_dp(ctrl->drm_dev, "link training #1 on phy %d successful\n", dp_phy); - ret = msm_dp_ctrl_link_train_2(ctrl, panel, training_step, dp_phy); + ret = msm_dp_ctrl_link_train_2(ctrl, training_step, dp_phy); if (ret) { DRM_ERROR("link training #2 on phy %d failed. ret=%d\n", dp_phy, ret); return ret; @@ -1652,42 +1785,56 @@ static int msm_dp_ctrl_link_train_1_2(struct msm_dp_ctrl_private *ctrl, } static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel, int *training_step) { int i; int ret = 0; - const u8 *dpcd = panel->dpcd; + const u8 *dpcd = ctrl->panel->dpcd; u8 encoding[] = { 0, DP_SET_ANSI_8B10B }; u8 assr; struct msm_dp_link_info link_info = {0}; - msm_dp_ctrl_config_ctrl_link(ctrl, panel); - msm_dp_ctrl_config_ctrl_streams(ctrl, panel); + ret = msm_dp_ctrl_phy_status(ctrl); + + if (ret) + return ret; + + msm_dp_ctrl_config_ctrl(ctrl); link_info.num_lanes = ctrl->link->link_params.num_lanes; link_info.rate = ctrl->link->link_params.rate; link_info.capabilities = DP_LINK_CAP_ENHANCED_FRAMING; msm_dp_aux_link_configure(ctrl->aux, &link_info); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + goto end; if (drm_dp_max_downspread(dpcd)) encoding[0] |= DP_SPREAD_AMP_0_5; /* config DOWNSPREAD_CTRL and MAIN_LINK_CHANNEL_CODING_SET */ drm_dp_dpcd_write(ctrl->aux, DP_DOWNSPREAD_CTRL, encoding, 2); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + goto end; if (drm_dp_alternate_scrambler_reset_cap(dpcd)) { assr = DP_ALTERNATE_SCRAMBLER_RESET_ENABLE; drm_dp_dpcd_write(ctrl->aux, DP_EDP_CONFIGURATION_SET, &assr, 1); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + goto end; } for (i = ctrl->link->lttpr_count - 1; i >= 0; i--) { enum drm_dp_phy dp_phy = DP_PHY_LTTPR(i); - ret = msm_dp_ctrl_link_train_1_2(ctrl, panel, training_step, dp_phy); - msm_dp_ctrl_clear_training_pattern(ctrl, panel, dp_phy); + ret = msm_dp_ctrl_link_train_1_2(ctrl, training_step, dp_phy); + if (!msm_dp_ctrl_phy_status(ctrl)) + msm_dp_ctrl_clear_training_pattern(ctrl, dp_phy); + ret = msm_dp_ctrl_phy_status(ctrl) ?: ret; if (ret) break; @@ -1698,7 +1845,7 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, goto end; } - ret = msm_dp_ctrl_link_train_1_2(ctrl, panel, training_step, DP_PHY_DPRX); + ret = msm_dp_ctrl_link_train_1_2(ctrl, training_step, DP_PHY_DPRX); if (ret) { DRM_ERROR("link training on sink failed. ret=%d\n", ret); goto end; @@ -1707,14 +1854,17 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, end: msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0); - return ret; + return msm_dp_ctrl_phy_status(ctrl) ?: ret; } static int msm_dp_ctrl_setup_main_link(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel, int *training_step) { - int ret = 0; + int ret; + + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; msm_dp_ctrl_mainlink_enable(ctrl); @@ -1727,7 +1877,7 @@ static int msm_dp_ctrl_setup_main_link(struct msm_dp_ctrl_private *ctrl, * a link training pattern, we have to first do soft reset. */ - ret = msm_dp_ctrl_link_train(ctrl, panel, training_step); + ret = msm_dp_ctrl_link_train(ctrl, training_step); return ret; } @@ -1739,6 +1889,7 @@ int msm_dp_ctrl_core_clk_enable(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + lockdep_assert_held(ctrl->state_lock); if (ctrl->core_clks_on) { drm_dbg_dp(ctrl->drm_dev, "core clks already enabled\n"); return 0; @@ -1765,6 +1916,13 @@ void msm_dp_ctrl_core_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + lockdep_assert_held(ctrl->state_lock); + if (WARN_ON_ONCE(ctrl->phy_init_owned || ctrl->phy_power_owned || + ctrl->link_clks_on || ctrl->stream_clks_on)) + return; + if (!ctrl->core_clks_on) + return; + clk_bulk_disable_unprepare(ctrl->num_core_clks, ctrl->core_clks); ctrl->core_clks_on = false; @@ -1783,6 +1941,7 @@ static int msm_dp_ctrl_link_clk_enable(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + lockdep_assert_held(ctrl->state_lock); if (ctrl->link_clks_on) { drm_dbg_dp(ctrl->drm_dev, "links clks already enabled\n"); return 0; @@ -1791,7 +1950,9 @@ static int msm_dp_ctrl_link_clk_enable(struct msm_dp_ctrl *msm_dp_ctrl) if (!ctrl->core_clks_on) { drm_dbg_dp(ctrl->drm_dev, "Enable core clks before link clks\n"); - msm_dp_ctrl_core_clk_enable(msm_dp_ctrl); + ret = msm_dp_ctrl_core_clk_enable(msm_dp_ctrl); + if (ret) + return ret; } ret = clk_bulk_prepare_enable(ctrl->num_link_clks, ctrl->link_clks); @@ -1815,6 +1976,10 @@ static void msm_dp_ctrl_link_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl) ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + lockdep_assert_held(ctrl->state_lock); + if (!ctrl->link_clks_on) + return; + clk_bulk_disable_unprepare(ctrl->num_link_clks, ctrl->link_clks); ctrl->link_clks_on = false; @@ -1827,34 +1992,65 @@ static void msm_dp_ctrl_link_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl) } static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel) + bool restore_xfers) { - int ret = 0; - struct phy *phy = ctrl->phy; - const u8 *dpcd = panel->dpcd; + const u8 *dpcd = ctrl->panel->dpcd; + bool had_power = ctrl->phy_power_owned; + bool xfers; + + int ret, cleanup; + + lockdep_assert_held(ctrl->state_lock); + xfers = msm_dp_aux_enable_xfers(ctrl->aux, false); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; + if (!ctrl->phy_init_owned) + return -EINVAL; ctrl->phy_opts.dp.lanes = ctrl->link->link_params.num_lanes; ctrl->phy_opts.dp.link_rate = ctrl->link->link_params.rate / 100; ctrl->phy_opts.dp.ssc = drm_dp_max_downspread(dpcd); - phy_configure(phy, &ctrl->phy_opts); - phy_power_on(phy); + ret = msm_dp_ctrl_configure_phy(ctrl); + if (ret) + return ret; + ret = msm_dp_ctrl_phy_power_get(ctrl); + if (ret) + return ret; if (!ctrl->clk_defaults_set) { ret = of_clk_set_defaults(ctrl->dev->of_node, false); if (ret) - return ret; + goto err_power; ctrl->clk_defaults_set = true; } - dev_pm_opp_set_rate(ctrl->dev, ctrl->link->link_params.rate * 1000); - ret = msm_dp_ctrl_link_clk_enable(&ctrl->msm_dp_ctrl); + ret = dev_pm_opp_set_rate(ctrl->dev, ctrl->link->link_params.rate * 1000); if (ret) + goto err_power; + ret = msm_dp_ctrl_link_clk_enable(&ctrl->msm_dp_ctrl); + if (ret) { DRM_ERROR("Unable to start link clocks. ret=%d\n", ret); + goto err_power; + } drm_dbg_dp(ctrl->drm_dev, "link rate=%d\n", ctrl->link->link_params.rate); + return msm_dp_ctrl_restore_aux(ctrl, xfers || restore_xfers); + +err_power: + msm_dp_ctrl_phy_failed(ctrl, ret, false); + /* A failed attempt must not put an older caller's successful power ref. */ + if (!had_power) { + cleanup = msm_dp_ctrl_phy_power_put(ctrl); + if (cleanup) { + DRM_ERROR("mainlink setup failed: %d; PHY cleanup failed: %d\n", + ret, cleanup); + return cleanup; + } + } return ret; } @@ -1891,14 +2087,17 @@ static void msm_dp_ctrl_psr_exit(struct msm_dp_ctrl_private *ctrl) msm_dp_write_link(ctrl, REG_PSR_CMD, cmd); } -void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel) +void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); u32 cfg; - if (!panel->psr_cap.version) + lockdep_assert_held(ctrl->state_lock); + if (msm_dp_ctrl_phy_status(ctrl) || !ctrl->phy_power_owned || + !ctrl->link_clks_on) + return; + if (!ctrl->panel->psr_cap.version) return; /* @@ -1920,15 +2119,19 @@ void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl, cfg = DP_PSR_ENABLE; drm_dp_dpcd_write(ctrl->aux, DP_PSR_EN_CFG, &cfg, 1); + msm_dp_ctrl_phy_status(ctrl); } -void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel, bool enter) +void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enter) { struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - if (!panel->psr_cap.version) + lockdep_assert_held(ctrl->state_lock); + if (msm_dp_ctrl_phy_status(ctrl) || !ctrl->phy_power_owned || + !ctrl->link_clks_on) + return; + if (!ctrl->panel->psr_cap.version) return; /* @@ -1974,39 +2177,153 @@ static void msm_dp_ctrl_phy_reset(struct msm_dp_ctrl_private *ctrl) msm_dp_write_ahb(ctrl, REG_DP_PHY_CTRL, 0x0); } -void msm_dp_ctrl_phy_init(struct msm_dp_ctrl *msm_dp_ctrl) +int msm_dp_ctrl_phy_init(struct msm_dp_ctrl *msm_dp_ctrl, bool *newly_owned) { struct msm_dp_ctrl_private *ctrl; - struct phy *phy; + bool xfers; + int ret; ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - phy = ctrl->phy; + lockdep_assert_held(ctrl->state_lock); + if (newly_owned) + *newly_owned = false; + xfers = msm_dp_aux_enable_xfers(ctrl->aux, false); + msm_dp_ctrl_clear_drained_phy_error(ctrl); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; + if (ctrl->phy_init_owned) + return msm_dp_ctrl_restore_aux(ctrl, xfers); + if (!ctrl->core_clks_on) + return -EIO; + + /* Controller reset remains before the new generic-PHY acquisition. */ msm_dp_ctrl_phy_reset(ctrl); - phy_init(phy); + ret = phy_init(ctrl->phy); + if (ret) + return msm_dp_ctrl_phy_failed(ctrl, ret, false); + ctrl->phy_init_owned = true; + if (newly_owned) + *newly_owned = true; + return msm_dp_ctrl_restore_aux(ctrl, xfers); } -void msm_dp_ctrl_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl) +int msm_dp_ctrl_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl; - struct phy *phy; + int ret; ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - phy = ctrl->phy; + lockdep_assert_held(ctrl->state_lock); + + msm_dp_aux_enable_xfers(ctrl->aux, false); + if (ctrl->phy_power_owned || ctrl->link_clks_on || ctrl->stream_clks_on) + return -EBUSY; + if (!ctrl->phy_init_owned) { + msm_dp_ctrl_clear_drained_phy_error(ctrl); + return 0; + } + if (!ctrl->core_clks_on) + return -EIO; msm_dp_ctrl_phy_reset(ctrl); - phy_exit(phy); + ret = phy_exit(ctrl->phy); + if (ret) + return msm_dp_ctrl_phy_failed(ctrl, ret, true); + ctrl->phy_init_owned = false; + msm_dp_ctrl_clear_drained_phy_error(ctrl); + return 0; } -static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel) +bool msm_dp_ctrl_has_phy_refs(struct msm_dp_ctrl *msm_dp_ctrl) { - struct phy *phy = ctrl->phy; - int ret = 0; + struct msm_dp_ctrl_private *ctrl = + + container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + lockdep_assert_held(ctrl->state_lock); + return ctrl->phy_init_owned || ctrl->phy_power_owned; +} + +bool msm_dp_ctrl_has_power_ref(struct msm_dp_ctrl *msm_dp_ctrl) +{ + struct msm_dp_ctrl_private *ctrl = + + container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + lockdep_assert_held(ctrl->state_lock); + return ctrl->phy_power_owned; +} + +bool msm_dp_ctrl_has_phy_error(struct msm_dp_ctrl *msm_dp_ctrl) +{ + struct msm_dp_ctrl_private *ctrl = + + container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + lockdep_assert_held(ctrl->state_lock); + return ctrl->phy_error || ctrl->cleanup_error || + msm_dp_aux_get_fault(ctrl->aux); +} + +void msm_dp_ctrl_record_aux_fault(struct msm_dp_ctrl *msm_dp_ctrl, int error) +{ + struct msm_dp_ctrl_private *ctrl = + + container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + lockdep_assert_held(ctrl->state_lock); + if (error < 0) + msm_dp_ctrl_phy_failed(ctrl, error, false); +} + +bool msm_dp_ctrl_link_regs_accessible(struct msm_dp_ctrl *msm_dp_ctrl) +{ + struct msm_dp_ctrl_private *ctrl = + + container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + + lockdep_assert_held(ctrl->state_lock); + /* Register-clock ownership is distinct from sink/PHY readiness. */ + return ctrl->core_clks_on && ctrl->link_clks_on; +} + +static int msm_dp_ctrl_stop_link(struct msm_dp_ctrl_private *ctrl, bool reset) +{ + lockdep_assert_held(ctrl->state_lock); + msm_dp_aux_enable_xfers(ctrl->aux, false); + /* Link registers are not accessible on a pre-link acquisition failure. */ + if (ctrl->link_clks_on) { + msm_dp_ctrl_mainlink_disable(ctrl); + if (reset) + msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl); + } + if (ctrl->stream_clks_on) { + clk_disable_unprepare(ctrl->pixel_clk); + ctrl->stream_clks_on = false; + } + msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); + return msm_dp_ctrl_phy_power_put(ctrl); +} - msm_dp_ctrl_mainlink_disable(ctrl); +static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) +{ + bool xfers; + int ret; + + lockdep_assert_held(ctrl->state_lock); + + xfers = msm_dp_aux_enable_xfers(ctrl->aux, false); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; + if (ctrl->link_clks_on) + msm_dp_ctrl_mainlink_disable(ctrl); ctrl->phy_opts.dp.lanes = ctrl->link->link_params.num_lanes; - phy_configure(phy, &ctrl->phy_opts); + ret = msm_dp_ctrl_configure_phy(ctrl); + if (ret) + return ret; /* * Disable and re-enable the mainlink clock since the @@ -2015,11 +2332,13 @@ static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl, */ msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); - phy_power_off(phy); + ret = msm_dp_ctrl_phy_power_put(ctrl); + if (ret) + return ret; /* hw recommended delay before re-enabling clocks */ msleep(20); - ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl, panel); + ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl, xfers); if (ret) { DRM_ERROR("Failed to enable mainlink clks. ret=%d\n", ret); return ret; @@ -2028,44 +2347,52 @@ static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl, return ret; } -static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel) +static int msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) { - struct phy *phy; - - phy = ctrl->phy; - - msm_dp_ctrl_mainlink_disable(ctrl); - - msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); - - msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); - - phy_power_off(phy); + bool xfers, had_init; + int ret; - /* aux channel down, reinit phy */ - phy_exit(phy); - phy_init(phy); + lockdep_assert_held(ctrl->state_lock); - return 0; + had_init = ctrl->phy_init_owned; + xfers = msm_dp_aux_enable_xfers(ctrl->aux, false); + ret = msm_dp_ctrl_stop_link(ctrl, true); + if (ret) + return ret; + if (!had_init) + return 0; + ret = msm_dp_ctrl_phy_exit(&ctrl->msm_dp_ctrl); + if (ret) + return ret; + /* Recreate AUX only after every old reference was successfully released. */ + ret = msm_dp_ctrl_phy_init(&ctrl->msm_dp_ctrl, NULL); + if (!ret && xfers) + ret = msm_dp_ctrl_restore_aux(ctrl, true); + return ret; } -static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel) +static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl) { int ret = 0; int training_step = DP_TRAINING_NONE; + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; + msm_dp_ctrl_push_idle(&ctrl->msm_dp_ctrl); ctrl->link->phy_params.p_level = 0; ctrl->link->phy_params.v_level = 0; - ret = msm_dp_ctrl_setup_main_link(ctrl, panel, &training_step); + ret = msm_dp_ctrl_setup_main_link(ctrl, &training_step); if (ret) goto end; - msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); + msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + goto end; msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO); @@ -2157,23 +2484,30 @@ static void msm_dp_ctrl_send_phy_pattern(struct msm_dp_ctrl_private *ctrl, } } -static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl) { bool success = false; + int ret; u32 pattern_sent = 0x0; u32 pattern_requested = ctrl->link->phy_params.phy_test_pattern_sel; drm_dbg_dp(ctrl->drm_dev, "request: 0x%x\n", pattern_requested); - if (msm_dp_ctrl_set_vx_px(ctrl, + ret = msm_dp_ctrl_set_vx_px(ctrl, ctrl->link->phy_params.v_level, - ctrl->link->phy_params.p_level)) { + ctrl->link->phy_params.p_level); + if (ret) { DRM_ERROR("Failed to set v/p levels\n"); - return false; + return ret; } msm_dp_ctrl_send_phy_pattern(ctrl, pattern_requested); - msm_dp_ctrl_update_phy_vx_px(ctrl, DP_PHY_DPRX); + ret = msm_dp_ctrl_update_phy_vx_px(ctrl, DP_PHY_DPRX); + if (ret) + return ret; msm_dp_link_send_test_response(ctrl->link); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; pattern_sent = msm_dp_read_link(ctrl, REG_DP_MAINLINK_READY); @@ -2206,49 +2540,20 @@ static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_dp_ctrl_private *ctrl) drm_dbg_dp(ctrl->drm_dev, "%s: test->0x%x\n", success ? "success" : "failed", pattern_requested); - return success; + return success ? 0 : -EIO; } -static int msm_dp_ctrl_on_pixel_clk(struct msm_dp_ctrl_private *ctrl, unsigned long pixel_rate) +static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl) { int ret; + int cleanup; + bool xfers; + unsigned long pixel_rate; - ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000); - if (ret) { - DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret); - return ret; - } - - if (WARN_ON_ONCE(ctrl->stream_clks_on)) - return 0; + ret = msm_dp_ctrl_phy_status(ctrl); - ret = clk_prepare_enable(ctrl->pixel_clk); - if (ret) { - DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret); + if (ret) return ret; - } - ctrl->stream_clks_on = true; - - return ret; -} - -void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl) -{ - struct msm_dp_ctrl_private *ctrl; - - ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - - if (ctrl->stream_clks_on) { - clk_disable_unprepare(ctrl->pixel_clk); - ctrl->stream_clks_on = false; - } -} - -static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel) -{ - int ret; - unsigned long pixel_rate; if (!ctrl->link->phy_params.phy_test_pattern_sel) { drm_dbg_dp(ctrl->drm_dev, @@ -2261,37 +2566,65 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl * running. Add the global reset just before disabling the * link clocks and core clocks. */ - msm_dp_ctrl_off_pixel_clk(&ctrl->msm_dp_ctrl); - msm_dp_ctrl_off_link(&ctrl->msm_dp_ctrl, panel); + xfers = msm_dp_aux_enable_xfers(ctrl->aux, false); + ret = msm_dp_ctrl_off(&ctrl->msm_dp_ctrl); + if (ret) + return ret; - ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl, panel); + ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl, xfers); if (ret) { DRM_ERROR("failed to enable DP link controller\n"); return ret; } + pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock; + ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000); + if (ret) { + DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret); + goto err_stop; + } - pixel_rate = panel->msm_dp_mode.drm_mode.clock; - ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); - if (ret) - return ret; - - msm_dp_ctrl_send_phy_test_pattern(ctrl); + if (ctrl->stream_clks_on) { + drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n"); + } else { + ret = clk_prepare_enable(ctrl->pixel_clk); + if (ret) { + DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret); + goto err_stop; + } + ctrl->stream_clks_on = true; + } - return 0; + ret = msm_dp_ctrl_send_phy_test_pattern(ctrl); + if (!ret) { + ret = msm_dp_ctrl_phy_status(ctrl); + if (!ret) + return 0; + } +err_stop: + cleanup = msm_dp_ctrl_stop_link(ctrl, true); + if (cleanup) { + DRM_ERROR("PHY test failed: %d; cleanup failed: %d\n", ret, cleanup); + return cleanup; + } + return ret; } -void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel) +int msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl; u32 sink_request = 0x0; + int ret; if (!msm_dp_ctrl) { DRM_ERROR("invalid input\n"); - return; + return -EINVAL; } ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + lockdep_assert_held(ctrl->state_lock); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; sink_request = ctrl->link->sink_request; if (sink_request & (DP_TEST_LINK_PHY_TEST_PATTERN | @@ -2302,32 +2635,36 @@ void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl, "skip sink request %#x with link_clks=%d stream_clks=%d\n", sink_request, ctrl->link_clks_on, ctrl->stream_clks_on); - return; + return 0; } } if (sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { drm_dbg_dp(ctrl->drm_dev, "PHY_TEST_PATTERN request\n"); - if (msm_dp_ctrl_process_phy_test_request(ctrl, panel)) { + ret = msm_dp_ctrl_process_phy_test_request(ctrl); + if (ret) { DRM_ERROR("process phy_test_req failed\n"); - return; + return ret; } } if (sink_request & DP_LINK_STATUS_UPDATED) { - if (msm_dp_ctrl_link_maintenance(ctrl, panel)) { + ret = msm_dp_ctrl_link_maintenance(ctrl); + if (ret) { DRM_ERROR("LM failed: TEST_LINK_TRAINING\n"); - return; + return ret; } } if (sink_request & DP_TEST_LINK_TRAINING) { msm_dp_link_send_test_response(ctrl->link); - if (msm_dp_ctrl_link_maintenance(ctrl, panel)) { + ret = msm_dp_ctrl_link_maintenance(ctrl); + if (ret) { DRM_ERROR("LM failed: TEST_LINK_TRAINING\n"); - return; + return ret; } } + return msm_dp_ctrl_phy_status(ctrl); } static bool msm_dp_ctrl_clock_recovery_any_ok( @@ -2353,14 +2690,19 @@ static bool msm_dp_ctrl_channel_eq_ok(struct msm_dp_ctrl_private *ctrl) { u8 link_status[DP_LINK_STATUS_SIZE]; int num_lanes = ctrl->link->link_params.num_lanes; + int ret; + + if (msm_dp_ctrl_phy_status(ctrl)) + return false; - drm_dp_dpcd_read_link_status(ctrl->aux, link_status); + ret = drm_dp_dpcd_read_link_status(ctrl->aux, link_status); + if (ret < 0 || msm_dp_ctrl_phy_status(ctrl)) + return false; return drm_dp_channel_eq_ok(link_status, num_lanes); } -int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel) +int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, bool restore_xfers) { int rc = 0; struct msm_dp_ctrl_private *ctrl; @@ -2370,16 +2712,27 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, u8 link_status[DP_LINK_STATUS_SIZE]; unsigned int training_step; unsigned long pixel_rate; + int cleanup; if (!msm_dp_ctrl) return -EINVAL; ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + lockdep_assert_held(ctrl->state_lock); + + restore_xfers |= msm_dp_aux_enable_xfers(ctrl->aux, false); + rc = msm_dp_ctrl_phy_status(ctrl); + if (rc) + return rc; + if (!ctrl->phy_init_owned) + return -EINVAL; - rate = panel->link_info.rate; - pixel_rate = panel->msm_dp_mode.drm_mode.clock; + rate = ctrl->panel->link_info.rate; + pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock; - msm_dp_ctrl_core_clk_enable(&ctrl->msm_dp_ctrl); + rc = msm_dp_ctrl_core_clk_enable(&ctrl->msm_dp_ctrl); + if (rc) + return rc; if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { drm_dbg_dp(ctrl->drm_dev, @@ -2389,8 +2742,8 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, } else { ctrl->link->link_params.rate = rate; ctrl->link->link_params.num_lanes = - panel->link_info.num_lanes; - if (panel->msm_dp_mode.out_fmt_is_yuv_420) + ctrl->panel->link_info.num_lanes; + if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) pixel_rate >>= 1; } @@ -2398,13 +2751,15 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, ctrl->link->link_params.rate, ctrl->link->link_params.num_lanes, pixel_rate); - rc = msm_dp_ctrl_enable_mainlink_clocks(ctrl, panel); + rc = msm_dp_ctrl_enable_mainlink_clocks(ctrl, restore_xfers); if (rc) return rc; while (--link_train_max_retries) { training_step = DP_TRAINING_NONE; - rc = msm_dp_ctrl_setup_main_link(ctrl, panel, &training_step); + rc = msm_dp_ctrl_setup_main_link(ctrl, &training_step); + if (msm_dp_ctrl_phy_status(ctrl)) + break; if (rc == 0) { /* training completed successfully */ break; @@ -2414,6 +2769,9 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, break; drm_dp_dpcd_read_link_status(ctrl->aux, link_status); + rc = msm_dp_ctrl_phy_status(ctrl); + if (rc) + break; rc = msm_dp_ctrl_link_rate_down_shift(ctrl); if (rc < 0) { /* already in RBR = 1.6G */ @@ -2423,7 +2781,7 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, * some lanes are ready, * reduce lane number */ - rc = msm_dp_ctrl_link_lane_down_shift(ctrl, panel); + rc = msm_dp_ctrl_link_lane_down_shift(ctrl); if (rc < 0) { /* lane == 1 already */ /* end with failure */ break; @@ -2439,12 +2797,15 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, break; drm_dp_dpcd_read_link_status(ctrl->aux, link_status); + rc = msm_dp_ctrl_phy_status(ctrl); + if (rc) + break; if (!drm_dp_clock_recovery_ok(link_status, ctrl->link->link_params.num_lanes)) rc = msm_dp_ctrl_link_rate_down_shift(ctrl); else - rc = msm_dp_ctrl_link_lane_down_shift(ctrl, panel); + rc = msm_dp_ctrl_link_lane_down_shift(ctrl); if (rc < 0) { /* end with failure */ @@ -2452,16 +2813,20 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, } /* stop link training before start re training */ - msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); + msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + rc = msm_dp_ctrl_phy_status(ctrl); + if (rc) + break; } - rc = msm_dp_ctrl_reinitialize_mainlink(ctrl, panel); + rc = msm_dp_ctrl_reinitialize_mainlink(ctrl); if (rc) { DRM_ERROR("Failed to reinitialize mainlink. rc=%d\n", rc); break; } } + rc = msm_dp_ctrl_phy_status(ctrl) ?: rc; if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) return rc; @@ -2476,21 +2841,26 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, * link training failed * end txing train pattern here */ - msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); - - msm_dp_ctrl_deinitialize_mainlink(ctrl, panel); - rc = -ECONNRESET; + if (!msm_dp_ctrl_phy_status(ctrl)) + msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + + rc = msm_dp_ctrl_phy_status(ctrl) ?: -ECONNRESET; + cleanup = msm_dp_ctrl_deinitialize_mainlink(ctrl); + if (cleanup) { + DRM_ERROR("link training failed: %d; cleanup failed: %d\n", + rc, cleanup); + rc = cleanup; + } } return rc; } -static int msm_dp_ctrl_link_retrain(struct msm_dp_ctrl_private *ctrl, - struct msm_dp_panel *panel) +static int msm_dp_ctrl_link_retrain(struct msm_dp_ctrl_private *ctrl) { int training_step = DP_TRAINING_NONE; - return msm_dp_ctrl_setup_main_link(ctrl, panel, &training_step); + return msm_dp_ctrl_setup_main_link(ctrl, &training_step); } static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, @@ -2561,67 +2931,84 @@ static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl, msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID, nvid); } -int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel, - bool force_link_train) +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train) { int ret = 0; + bool mainlink_ready = false; + bool xfers; struct msm_dp_ctrl_private *ctrl; + unsigned long pixel_rate; + unsigned long pixel_rate_orig; + int cleanup; if (!msm_dp_ctrl) return -EINVAL; ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); + lockdep_assert_held(ctrl->state_lock); - drm_dbg_dp(ctrl->drm_dev, "rate=%d, num_lanes=%d\n", - ctrl->link->link_params.rate, - ctrl->link->link_params.num_lanes); + xfers = msm_dp_aux_enable_xfers(ctrl->aux, false); + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; + if (!ctrl->phy_init_owned || (ctrl->link_clks_on && !ctrl->phy_power_owned)) + return -EINVAL; + ret = msm_dp_ctrl_restore_aux(ctrl, xfers); + if (ret) + return ret; + + pixel_rate = pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock; + + if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) + pixel_rate >>= 1; + + drm_dbg_dp(ctrl->drm_dev, "rate=%d, num_lanes=%d, pixel_rate=%lu\n", + ctrl->link->link_params.rate, + ctrl->link->link_params.num_lanes, pixel_rate); drm_dbg_dp(ctrl->drm_dev, "core_clk_on=%d link_clk_on=%d stream_clk_on=%d\n", ctrl->core_clks_on, ctrl->link_clks_on, ctrl->stream_clks_on); if (!ctrl->link_clks_on) { /* link clk is off */ - ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl, panel); + ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl, false); if (ret) { DRM_ERROR("Failed to start link clocks. ret=%d\n", ret); - return ret; + goto end; } } - if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl)) - msm_dp_ctrl_link_retrain(ctrl, panel); - - /* stop txing train pattern to end link training */ - msm_dp_ctrl_clear_training_pattern(ctrl, panel, DP_PHY_DPRX); - - return ret; -} - -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel) -{ - int ret = 0; - bool mainlink_ready = false; - struct msm_dp_ctrl_private *ctrl; - unsigned long pixel_rate; - unsigned long pixel_rate_orig; - - if (!msm_dp_ctrl) - return -EINVAL; - - ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - - pixel_rate_orig = panel->msm_dp_mode.drm_mode.clock; - pixel_rate = pixel_rate_orig; + ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000); + if (ret) { + DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret); + goto end; + } - if (msm_dp_ctrl->wide_bus_en || panel->msm_dp_mode.out_fmt_is_yuv_420) - pixel_rate >>= 1; + if (ctrl->stream_clks_on) { + drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n"); + } else { + ret = clk_prepare_enable(ctrl->pixel_clk); + if (ret) { + DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret); + goto end; + } + ctrl->stream_clks_on = true; + } - drm_dbg_dp(ctrl->drm_dev, "pixel_rate=%lu\n", pixel_rate); + if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl)) { + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + goto end; + ret = msm_dp_ctrl_link_retrain(ctrl); + if (ret) + goto end; + } - ret = msm_dp_ctrl_on_pixel_clk(ctrl, pixel_rate); + /* stop txing train pattern to end link training */ + msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); + ret = msm_dp_ctrl_phy_status(ctrl); if (ret) - return ret; + goto end; /* * Set up transfer unit values and set controller state to send @@ -2629,69 +3016,91 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel * */ reinit_completion(&ctrl->video_comp); - msm_dp_ctrl_lane_mapping(ctrl); - msm_dp_setup_peripheral_flush(ctrl); - msm_dp_ctrl_config_ctrl_link(ctrl, panel); - - msm_dp_ctrl_configure_source_params(ctrl, panel); + msm_dp_ctrl_configure_source_params(ctrl); msm_dp_ctrl_config_msa(ctrl, ctrl->link->link_params.rate, pixel_rate_orig, - panel->msm_dp_mode.out_fmt_is_yuv_420); + ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420); - msm_dp_panel_clear_dsc_dto(panel); + msm_dp_panel_clear_dsc_dto(ctrl->panel); - msm_dp_ctrl_setup_tr_unit(ctrl, panel); + msm_dp_ctrl_setup_tr_unit(ctrl); msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO); ret = msm_dp_ctrl_wait4video_ready(ctrl); if (ret) - return ret; + goto end; mainlink_ready = msm_dp_ctrl_mainlink_ready(ctrl); drm_dbg_dp(ctrl->drm_dev, "mainlink %s\n", mainlink_ready ? "READY" : "NOT READY"); + if (!mainlink_ready) { + ret = msm_dp_ctrl_phy_status(ctrl) ?: -EIO; + goto end; + } + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + goto end; + return ret; +end: + /* The stream-success flag is not an ownership ledger for partial setup. */ + cleanup = msm_dp_ctrl_stop_link(ctrl, true); + if (cleanup) { + DRM_ERROR("stream setup failed: %d; cleanup failed: %d\n", ret, cleanup); + ret = cleanup; + } + drm_dbg_dp(ctrl->drm_dev, "preempted exit: mainlink %s, ret = %x\n", + mainlink_ready ? "READY" : "NOT READY", ret); return ret; } -void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl) +int msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl; - struct phy *phy; + bool xfers, had_init; + int ret; ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - phy = ctrl->phy; + lockdep_assert_held(ctrl->state_lock); + + had_init = ctrl->phy_init_owned; - /* aux channel down, reinit phy */ - phy_exit(phy); - phy_init(phy); + /* Preserve SDP -> sink D3 -> controller stop -> PHY off ordering. */ + if (ctrl->link_clks_on) { + msm_dp_panel_disable_vsc_sdp(ctrl->panel); + if (!msm_dp_ctrl_phy_status(ctrl) && ctrl->phy_init_owned) + msm_dp_link_psm_config(ctrl->link, &ctrl->panel->link_info, true); + } + xfers = msm_dp_aux_enable_xfers(ctrl->aux, false); + ret = msm_dp_ctrl_stop_link(ctrl, false); + if (ret) + return ret; + if (!had_init) + return 0; + ret = msm_dp_ctrl_phy_exit(msm_dp_ctrl); + if (ret) + return ret; + ret = msm_dp_ctrl_phy_init(msm_dp_ctrl, NULL); + if (!ret && xfers) + ret = msm_dp_ctrl_restore_aux(ctrl, true); + return ret; } -void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel) +int msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl; - struct phy *phy; ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - phy = ctrl->phy; - - msm_dp_panel_disable_vsc_sdp(panel); - - msm_dp_ctrl_mainlink_disable(ctrl); - - msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl, panel); - - msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); - - phy_power_off(phy); + lockdep_assert_held(ctrl->state_lock); + if (ctrl->link_clks_on) + msm_dp_panel_disable_vsc_sdp(ctrl->panel); + return msm_dp_ctrl_stop_link(ctrl, true); } -irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel) +irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl) { struct msm_dp_ctrl_private *ctrl; u32 isr; @@ -2702,7 +3111,7 @@ irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl); - if (panel->psr_cap.version) { + if (ctrl->panel->psr_cap.version) { isr = msm_dp_ctrl_get_psr_interrupt(ctrl); if (isr) @@ -2791,15 +3200,16 @@ static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl) } struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link, - struct drm_dp_aux *aux, + struct msm_dp_panel *panel, struct drm_dp_aux *aux, struct phy *phy, + struct mutex *state_lock, void __iomem *ahb_base, void __iomem *link_base) { struct msm_dp_ctrl_private *ctrl; int ret; - if (!dev || !aux || !link) { + if (!dev || !panel || !aux || !link || !state_lock) { DRM_ERROR("invalid input\n"); return ERR_PTR(-EINVAL); } @@ -2827,10 +3237,12 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link init_completion(&ctrl->video_comp); /* in parameters */ + ctrl->panel = panel; ctrl->aux = aux; ctrl->link = link; ctrl->dev = dev; ctrl->phy = phy; + ctrl->state_lock = state_lock; ctrl->ahb_base = ahb_base; ctrl->link_base = link_base; diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h index 5902cf7e746a48..ec7916c280807e 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -15,38 +15,41 @@ struct msm_dp_ctrl { }; struct phy; +struct mutex; -int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel); -int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, struct msm_dp_panel *panel); -int msm_dp_ctrl_prepare_stream_on(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel, - bool force_link_train); -void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel); -void msm_dp_ctrl_off_pixel_clk(struct msm_dp_ctrl *msm_dp_ctrl); +/* + * Resource transitions and ownership/error queries require the state_lock + * supplied to msm_dp_ctrl_get(). IRQ/completion handling remains lock-free. + */ +int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl, bool restore_xfers); +int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train); +int msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl); +int msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl); -irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel); -void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel); +irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl); +int msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl); struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link, + struct msm_dp_panel *panel, struct drm_dp_aux *aux, struct phy *phy, + struct mutex *state_lock, void __iomem *ahb_base, void __iomem *link_base); -void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel); -void msm_dp_ctrl_phy_init(struct msm_dp_ctrl *msm_dp_ctrl); -void msm_dp_ctrl_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl); +void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl); +int msm_dp_ctrl_phy_init(struct msm_dp_ctrl *msm_dp_ctrl, bool *newly_owned); +int msm_dp_ctrl_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl); +bool msm_dp_ctrl_has_phy_refs(struct msm_dp_ctrl *msm_dp_ctrl); +bool msm_dp_ctrl_has_power_ref(struct msm_dp_ctrl *msm_dp_ctrl); +bool msm_dp_ctrl_has_phy_error(struct msm_dp_ctrl *msm_dp_ctrl); +/* Record a negative AUX fault without clearing the AUX owner's latch. */ +void msm_dp_ctrl_record_aux_fault(struct msm_dp_ctrl *msm_dp_ctrl, int error); +bool msm_dp_ctrl_link_regs_accessible(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_irq_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl); -void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel, bool enable); -void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl, - struct msm_dp_panel *panel); +void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enable); +void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl); int msm_dp_ctrl_core_clk_enable(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_core_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl); @@ -54,5 +57,4 @@ void msm_dp_ctrl_core_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl); void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl); -void msm_dp_ctrl_reinit_phy(struct msm_dp_ctrl *msm_dp_ctrl); #endif /* _DP_CTRL_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 76aee2fcd30c5b..37658536708baf 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.c +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -12,9 +12,13 @@ #include #include #include +#include #include #include #include +#include +#include +#include #include "msm_drv.h" #include "msm_kms.h" @@ -50,11 +54,38 @@ struct msm_dp_display_private { /* state variables */ bool core_initialized; - bool phy_initialized; bool audio_supported; - struct mutex plugged_lock; + /* Parent runtime PM is acquired before this lock, and put after it. */ + struct mutex state_lock; bool plugged; + bool plugged_pm_held; + bool stream_pm_held; + bool hpd_pm_held; + bool cleanup_pm_held; + + /* Desired AUX policy is not PHY ownership or the current transfer gate. */ + bool aux_demand_enabled; + bool aux_restore_pending; + bool aux_bus_populated; + bool aux_registered; + bool aux_owner_attached; + bool runtime_pm_enabled; + bool irq_requested; + bool irq_enabled; + + /* Never held by bind/unbind; component_add may synchronously bind. */ + struct mutex probe_lock; + bool removing; + bool component_added; + struct work_struct link_status_work; + struct work_struct audio_notify_work; + bool audio_notify_pending; + bool audio_plugged; + bool link_status_stopping; + bool link_status_bad; + bool link_status_notify; + int link_error; struct drm_device *drm_dev; @@ -63,11 +94,9 @@ struct msm_dp_display_private { struct msm_dp_panel *panel; struct msm_dp_ctrl *ctrl; + struct msm_dp_display_mode msm_dp_mode; struct msm_dp msm_dp_display; - /* wait for audio signaling */ - struct completion audio_comp; - /* HPD IRQ handling */ spinlock_t irq_thread_lock; u32 hpd_isr_status; @@ -95,6 +124,191 @@ struct msm_dp_desc { bool wide_bus_supported; }; +static int msm_dp_display_start_aux_owner(struct msm_dp_display_private *dp); + +static void msm_dp_display_audio_notify_work(struct work_struct *work) +{ + struct msm_dp_display_private *dp = container_of(work, + struct msm_dp_display_private, audio_notify_work); + struct drm_connector *connector; + struct drm_device *dev; + bool plugged; + + for (;;) { + mutex_lock(&dp->state_lock); + connector = dp->msm_dp_display.connector; + if (dp->link_status_stopping || !dp->audio_notify_pending || !connector) { + dp->audio_notify_pending = false; + mutex_unlock(&dp->state_lock); + return; + } + plugged = dp->audio_plugged; + dp->audio_notify_pending = false; + dev = connector->dev; + drm_dev_get(dev); + drm_connector_get(connector); + mutex_unlock(&dp->state_lock); + + /* ALSA jack/DAPM callbacks may re-enter audio prepare/shutdown. */ + drm_connector_hdmi_audio_plugged_notify(connector, plugged); + drm_connector_put(connector); + drm_dev_put(dev); + } +} + +static void msm_dp_display_handle_plugged_change(struct msm_dp *msm_dp_display, + bool plugged) +{ + struct msm_dp_display_private *dp = container_of(msm_dp_display, + struct msm_dp_display_private, msm_dp_display); + + lockdep_assert_held(&dp->state_lock); + if (dp->audio_supported && !dp->link_status_stopping && + msm_dp_display->connector) { + dp->audio_plugged = plugged; + dp->audio_notify_pending = true; + schedule_work(&dp->audio_notify_work); + } +} + +static void msm_dp_display_link_status_work(struct work_struct *work) +{ + struct msm_dp_display_private *dp = container_of(work, + struct msm_dp_display_private, link_status_work); + struct drm_connector *connector; + struct drm_device *dev; + + bool notify = false; + + mutex_lock(&dp->state_lock); + connector = dp->msm_dp_display.connector; + if (dp->link_status_stopping || !dp->link_status_bad || !connector) { + mutex_unlock(&dp->state_lock); + return; + } + dev = connector->dev; + drm_dev_get(dev); + drm_connector_get(connector); + mutex_unlock(&dp->state_lock); + + /* DRM detection takes connection_mutex before the display state lock. */ + drm_modeset_lock(&dev->mode_config.connection_mutex, NULL); + mutex_lock(&dp->state_lock); + if (!dp->link_status_stopping && dp->link_status_bad && + connector == dp->msm_dp_display.connector && connector->state) { + connector->state->link_status = DRM_MODE_LINK_STATUS_BAD; + notify = dp->link_status_notify; + dp->link_status_bad = false; + dp->link_status_notify = false; + } + mutex_unlock(&dp->state_lock); + drm_modeset_unlock(&dev->mode_config.connection_mutex); + + if (notify) + drm_kms_helper_connector_hotplug_event(connector); + drm_connector_put(connector); + drm_dev_put(dev); +} + +static void msm_dp_display_link_failed(struct msm_dp_display_private *dp, + int error, bool notify) +{ + lockdep_assert_held(&dp->state_lock); + dp->link_error = error; + if (!dp->link_status_stopping && dp->msm_dp_display.connector) { + dp->link_status_bad = true; + dp->link_status_notify |= notify; + schedule_work(&dp->link_status_work); + } +} + +static void msm_dp_display_stop_link_status(struct msm_dp_display_private *dp) +{ + mutex_lock(&dp->state_lock); + dp->link_status_stopping = true; + dp->aux_demand_enabled = false; + dp->aux_restore_pending = false; + msm_dp_aux_owner_stop(dp->aux); + if (dp->core_initialized) + msm_dp_aux_hpd_intr_disable(dp->aux); + dp->link_status_bad = false; + dp->link_status_notify = false; + dp->audio_notify_pending = false; + mutex_unlock(&dp->state_lock); + if (dp->irq_requested) + synchronize_irq(dp->irq); + /* A fault worker must account for its transferred PM lease, not be cancelled. */ + msm_dp_aux_owner_drain(dp->aux); + cancel_work_sync(&dp->link_status_work); + cancel_work_sync(&dp->audio_notify_work); +} + +/* These flags count successful parent-PM leases, not connection/hardware state. */ +static bool msm_dp_display_has_pm_role(struct msm_dp_display_private *dp) +{ + lockdep_assert_held(&dp->state_lock); + return dp->plugged_pm_held || dp->stream_pm_held || dp->hpd_pm_held; +} + +static bool msm_dp_display_needs_cleanup_pm(struct msm_dp_display_private *dp) +{ + lockdep_assert_held(&dp->state_lock); + /* Healthy init-only ownership can be drained by runtime_suspend itself. */ + return msm_dp_aux_get_fault(dp->aux) || dp->msm_dp_display.audio_enabled || + (msm_dp_ctrl_has_phy_refs(dp->ctrl) && + (msm_dp_ctrl_has_power_ref(dp->ctrl) || + msm_dp_ctrl_has_phy_error(dp->ctrl))); +} + +static void msm_dp_display_keep_cleanup_pm(struct msm_dp_display_private *dp, + bool *put_temporary) +{ + lockdep_assert_held(&dp->state_lock); + if (*put_temporary && msm_dp_display_needs_cleanup_pm(dp) && + !msm_dp_display_has_pm_role(dp) && !dp->cleanup_pm_held) { + dp->cleanup_pm_held = true; + *put_temporary = false; + } +} + +static void msm_dp_display_release_pm_role(struct msm_dp_display_private *dp, + bool *role, unsigned int *puts) +{ + lockdep_assert_held(&dp->state_lock); + if (!*role) + return; + *role = false; + if (msm_dp_display_needs_cleanup_pm(dp) && + !msm_dp_display_has_pm_role(dp) && !dp->cleanup_pm_held) + dp->cleanup_pm_held = true; + else + (*puts)++; +} + +static void msm_dp_display_release_cleanup_pm(struct msm_dp_display_private *dp, + unsigned int *puts) +{ + lockdep_assert_held(&dp->state_lock); + if (dp->cleanup_pm_held && (!msm_dp_display_needs_cleanup_pm(dp) || + msm_dp_display_has_pm_role(dp))) { + dp->cleanup_pm_held = false; + (*puts)++; + } +} + +static void msm_dp_display_put_pm(struct msm_dp_display_private *dp, + unsigned int puts) +{ + int ret; + + /* A failing put_sync still consumes its usage reference. */ + while (puts--) { + ret = pm_runtime_put_sync(&dp->msm_dp_display.pdev->dev); + if (ret < 0) + drm_dbg_dp(dp->drm_dev, "runtime idle refused: %d\n", ret); + } +} + static const struct msm_dp_desc msm_dp_desc_glymur[] = { { .io_start = 0x0af54000, .id = MSM_DP_CONTROLLER_0, .wide_bus_supported = true }, { .io_start = 0x0af5c000, .id = MSM_DP_CONTROLLER_1, .wide_bus_supported = true }, @@ -184,22 +398,18 @@ static struct msm_dp_display_private *dev_get_dp_display_private(struct device * return container_of(dp, struct msm_dp_display_private, msm_dp_display); } -void msm_dp_display_signal_audio_start(struct msm_dp *msm_dp_display) -{ - struct msm_dp_display_private *dp; - - dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - - reinit_completion(&dp->audio_comp); -} - -void msm_dp_display_signal_audio_complete(struct msm_dp *msm_dp_display) +static void msm_dp_display_clear_binding(struct msm_dp_display_private *dp, + struct msm_drm_private *priv) { - struct msm_dp_display_private *dp; - - dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - - complete_all(&dp->audio_comp); + /* Call only after notification, IRQ and raw-owner drains. */ + mutex_lock(&dp->state_lock); + dp->msm_dp_display.connector = NULL; + dp->panel->connector = NULL; + dp->msm_dp_display.drm_dev = NULL; + dp->drm_dev = NULL; + dp->aux->drm_dev = NULL; + priv->kms->dp[dp->id] = NULL; + mutex_unlock(&dp->state_lock); } static int msm_dp_display_bind(struct device *dev, struct device *master, @@ -210,19 +420,28 @@ static int msm_dp_display_bind(struct device *dev, struct device *master, struct msm_drm_private *priv = dev_get_drvdata(master); struct drm_device *drm = priv->dev; + rc = msm_dp_display_start_aux_owner(dp); + if (rc) + return rc; + mutex_lock(&dp->state_lock); dp->msm_dp_display.drm_dev = drm; priv->kms->dp[dp->id] = &dp->msm_dp_display; - dp->drm_dev = drm; dp->aux->drm_dev = drm; + dp->link_status_stopping = false; + mutex_unlock(&dp->state_lock); rc = msm_dp_aux_register(dp->aux); if (rc) { DRM_ERROR("DRM DP AUX register failed\n"); goto end; } + dp->aux_registered = true; return 0; end: + /* Component core does not unbind the component whose bind failed. */ + msm_dp_display_stop_link_status(dp); + msm_dp_display_clear_binding(dp, priv); return rc; } @@ -232,12 +451,11 @@ static void msm_dp_display_unbind(struct device *dev, struct device *master, struct msm_dp_display_private *dp = dev_get_dp_display_private(dev); struct msm_drm_private *priv = dev_get_drvdata(master); - of_dp_aux_depopulate_bus(dp->aux); - + msm_dp_display_stop_link_status(dp); + /* AUX children belong to the platform device, not this aggregate binding. */ msm_dp_aux_unregister(dp->aux); - dp->drm_dev = NULL; - dp->aux->drm_dev = NULL; - priv->kms->dp[dp->id] = NULL; + dp->aux_registered = false; + msm_dp_display_clear_binding(dp, priv); } static const struct component_ops msm_dp_display_comp_ops = { @@ -268,7 +486,6 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) const struct drm_display_info *info = &connector->display_info; int rc = 0; u8 dpcd[DP_RECEIVER_CAP_SIZE]; - const struct drm_edid *drm_edid = NULL; rc = drm_dp_read_dpcd_caps(dp->aux, dpcd); if (rc) @@ -276,20 +493,10 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) dp->link->lttpr_count = msm_dp_display_lttpr_init(dp, dpcd); - rc = msm_dp_panel_read_link_caps(dp->panel, connector); + rc = msm_dp_panel_read_sink_caps(dp->panel, connector); if (rc) goto end; - drm_edid = drm_edid_read_ddc(connector, &dp->aux->ddc); - drm_edid_connector_update(connector, drm_edid); - - if (!drm_edid) { - DRM_ERROR("panel edid read failed\n"); - /* check edid read fail is due to unplug */ - if (!msm_dp_aux_is_link_connected(dp->aux)) - return -ETIMEDOUT; - } - msm_dp_link_process_request(dp->link); if (!dp->msm_dp_display.is_edp) @@ -301,7 +508,7 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) dp->msm_dp_display.psr_supported = dp->panel->psr_cap.version && psr_enabled; dp->audio_supported = info->has_audio; - msm_dp_panel_handle_sink_request(dp->panel, drm_edid); + msm_dp_panel_handle_sink_request(dp->panel); /* * set sink to normal operation mode -- D0 @@ -312,70 +519,259 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) msm_dp_link_reset_phy_params_vx_px(dp->link); end: - drm_edid_free(drm_edid); return rc; } -/** - * msm_dp_display_host_phy_init() - start up DP PHY - * @dp: main display data structure - * - * Prepare DP PHY for the AUX transactions to succeed. - * - * Returns: true if this call has initliazed the PHY and false if the PHY has - * already been setup beforehand. - */ -static bool msm_dp_display_host_phy_init(struct msm_dp_display_private *dp) +static void msm_dp_display_aux_pending(struct msm_dp_display_private *dp) +{ + lockdep_assert_held(&dp->state_lock); + if (dp->aux_demand_enabled) + dp->aux_restore_pending = true; +} + +static void msm_dp_display_aux_revoke(struct msm_dp_display_private *dp) { - drm_dbg_dp(dp->drm_dev, "type=%d core_init=%d phy_init=%d\n", - dp->msm_dp_display.connector_type, dp->core_initialized, - dp->phy_initialized); + lockdep_assert_held(&dp->state_lock); + dp->aux_demand_enabled = false; + dp->aux_restore_pending = false; + msm_dp_aux_enable_xfers(dp->aux, false); +} - if (!dp->phy_initialized) { - msm_dp_ctrl_phy_init(dp->ctrl); - dp->phy_initialized = true; - return true; +/* No sink transaction: this must also work for a faulting raw AUX caller. */ +static int msm_dp_display_drain_aux_fault(struct msm_dp_display_private *dp) +{ + int ret; + + lockdep_assert_held(&dp->state_lock); + + msm_dp_aux_enable_xfers(dp->aux, false); + msm_dp_display_aux_pending(dp); + dp->msm_dp_display.power_on = false; + ret = msm_dp_audio_stop(&dp->msm_dp_display); + if (ret) + return ret; + ret = msm_dp_ctrl_off(dp->ctrl); + if (ret) + return ret; + return msm_dp_ctrl_phy_exit(dp->ctrl); +} + +static int msm_dp_display_aux_admit(struct msm_dp_display_private *dp, + bool explicit, bool *newly_owned) +{ + int ret; + + lockdep_assert_held(&dp->state_lock); + + if (newly_owned) + *newly_owned = false; + if (dp->link_status_stopping) + return -ENODEV; + if (!dp->core_initialized) + return -EIO; + /* + * Drain fault publication before inspecting the worker's lease. With the + * gate closed and state held, no new transfer can latch another wave + * between fault_pending(), get_fault() and finish_fault(). + */ + msm_dp_aux_enable_xfers(dp->aux, false); + if (!dp->msm_dp_display.is_edp && + (!msm_dp_aux_is_link_connected(dp->aux) || (!explicit && !dp->plugged))) { + msm_dp_display_aux_revoke(dp); + return -ENOTCONN; } + if (explicit) { + dp->aux_demand_enabled = true; + dp->aux_restore_pending = true; + } + if (!dp->aux_demand_enabled || !dp->aux_restore_pending) + return -ENXIO; + /* Only the queued owner consumes/finishes its transferred reference. */ + if (msm_dp_aux_fault_pending(dp->aux)) + return -EAGAIN; + if (msm_dp_aux_get_fault(dp->aux)) { + ret = msm_dp_display_drain_aux_fault(dp); + if (ret) + return ret; + if (WARN_ON_ONCE(msm_dp_ctrl_has_phy_refs(dp->ctrl) || + dp->msm_dp_display.audio_enabled)) + return -EBUSY; + /* A prior worker retained the latch, but no longer owns a fault lease. */ + WARN_ON_ONCE(msm_dp_aux_finish_fault(dp->aux, true)); + } + ret = msm_dp_ctrl_phy_init(dp->ctrl, newly_owned); + if (!ret) + ret = msm_dp_aux_admit_xfers(dp->aux); + if (!ret) + dp->aux_restore_pending = false; + return ret; +} - return false; +static int msm_dp_display_host_phy_init(struct msm_dp_display_private *dp, + bool *newly_owned) +{ + return msm_dp_display_aux_admit(dp, true, newly_owned); } -static void msm_dp_display_host_phy_exit(struct msm_dp_display_private *dp) +static int msm_dp_display_raw_aux_admit(void *data, bool *put_temporary) { - drm_dbg_dp(dp->drm_dev, "type=%d core_init=%d phy_init=%d\n", - dp->msm_dp_display.connector_type, dp->core_initialized, - dp->phy_initialized); + struct msm_dp_display_private *dp = data; + unsigned int puts = 0; + int ret; - if (dp->phy_initialized) { - msm_dp_ctrl_phy_exit(dp->ctrl); - dp->phy_initialized = false; + /* DRM's hw_mutex may be held: state owners can be waiting for that mutex. */ + if (!mutex_trylock(&dp->state_lock)) + return -EAGAIN; + ret = msm_dp_display_aux_admit(dp, false, NULL); + if (ret) + msm_dp_display_keep_cleanup_pm(dp, put_temporary); + msm_dp_display_release_cleanup_pm(dp, &puts); + mutex_unlock(&dp->state_lock); + msm_dp_display_put_pm(dp, puts); + return ret; +} + +static void msm_dp_display_aux_recover(void *data) +{ + struct msm_dp_display_private *dp = data; + unsigned int puts = 0; + bool put_temporary, fully_drained; + int error, cleanup; + + /* AUX hands us the failing raw call's existing lease, without its locks. */ + mutex_lock(&dp->state_lock); + error = msm_dp_aux_get_fault(dp->aux); + msm_dp_ctrl_record_aux_fault(dp->ctrl, error); + cleanup = msm_dp_display_drain_aux_fault(dp); + fully_drained = !cleanup && !msm_dp_ctrl_has_phy_refs(dp->ctrl) && + !dp->msm_dp_display.audio_enabled; + msm_dp_display_handle_plugged_change(&dp->msm_dp_display, false); + msm_dp_display_link_failed(dp, error ?: cleanup, true); + /* Finish under state, so a new admission cannot race the old lease flag. */ + put_temporary = msm_dp_aux_finish_fault(dp->aux, fully_drained); + msm_dp_display_keep_cleanup_pm(dp, &put_temporary); + msm_dp_display_release_cleanup_pm(dp, &puts); + mutex_unlock(&dp->state_lock); + /* Even a failing put consumes this lease; do not put or adopt it twice. */ + msm_dp_display_put_pm(dp, puts + put_temporary); +} + +static const struct msm_dp_aux_owner_ops msm_dp_display_aux_ops = { + .admit = msm_dp_display_raw_aux_admit, + .recover = msm_dp_display_aux_recover, +}; + +static int msm_dp_display_start_aux_owner(struct msm_dp_display_private *dp) +{ + unsigned int puts = 0; + bool put_temporary = false; + int ret; + + mutex_lock(&dp->state_lock); + if (READ_ONCE(dp->removing)) { + ret = -ENODEV; + goto out; + } + if (dp->aux_owner_attached && !dp->link_status_stopping) { + ret = 0; + goto out; + } + if (dp->aux_owner_attached) { + /* Initial probe attaches before runtime PM is enabled. Rebind does not. */ + mutex_unlock(&dp->state_lock); + ret = pm_runtime_resume_and_get(&dp->msm_dp_display.pdev->dev); + if (ret < 0) + return ret; + put_temporary = true; + mutex_lock(&dp->state_lock); + if (READ_ONCE(dp->removing)) { + ret = -ENODEV; + goto out; + } + if (!dp->link_status_stopping) { + ret = 0; + goto out; + } + /* Stop/drain precedes component rebind; never steal a worker's lease. */ + msm_dp_aux_enable_xfers(dp->aux, false); + if (msm_dp_aux_fault_pending(dp->aux)) { + ret = -EAGAIN; + goto out; + } + /* Controller acquisition/cleanup failures may have no AUX latch. */ + if (msm_dp_ctrl_has_phy_error(dp->ctrl)) { + ret = msm_dp_display_drain_aux_fault(dp); + if (ret) + goto out; + if (WARN_ON_ONCE(msm_dp_ctrl_has_phy_refs(dp->ctrl) || + dp->msm_dp_display.audio_enabled)) { + ret = -EBUSY; + goto out; + } + WARN_ON_ONCE(msm_dp_aux_finish_fault(dp->aux, true)); + } } + ret = msm_dp_aux_owner_set(dp->aux, &msm_dp_display_aux_ops, dp); + if (!ret) { + dp->aux_owner_attached = true; + dp->link_status_stopping = false; + dp->aux_demand_enabled = dp->msm_dp_display.is_edp; + dp->aux_restore_pending = dp->aux_demand_enabled; + } +out: + if (put_temporary) + msm_dp_display_keep_cleanup_pm(dp, &put_temporary); + msm_dp_display_release_cleanup_pm(dp, &puts); + mutex_unlock(&dp->state_lock); + msm_dp_display_put_pm(dp, puts + put_temporary); + return ret; +} + +static int msm_dp_display_host_phy_exit(struct msm_dp_display_private *dp) +{ + int ret; + + lockdep_assert_held(&dp->state_lock); + + msm_dp_display_aux_pending(dp); + ret = msm_dp_audio_stop(&dp->msm_dp_display); + if (ret) + return ret; + return msm_dp_ctrl_phy_exit(dp->ctrl); } -static void msm_dp_display_host_init(struct msm_dp_display_private *dp) +static int msm_dp_display_host_init(struct msm_dp_display_private *dp) { - drm_dbg_dp(dp->drm_dev, "type=%d core_init=%d phy_init=%d\n", - dp->msm_dp_display.connector_type, dp->core_initialized, - dp->phy_initialized); + int ret; + + lockdep_assert_held(&dp->state_lock); - msm_dp_ctrl_core_clk_enable(dp->ctrl); - msm_dp_ctrl_reset(dp->ctrl, dp->panel); + if (dp->core_initialized) + return 0; + ret = msm_dp_ctrl_core_clk_enable(dp->ctrl); + if (ret) + return ret; + msm_dp_ctrl_reset(dp->ctrl); msm_dp_ctrl_enable_irq(dp->ctrl); msm_dp_aux_init(dp->aux); dp->core_initialized = true; + return 0; } -static void msm_dp_display_host_deinit(struct msm_dp_display_private *dp) +static int msm_dp_display_host_deinit(struct msm_dp_display_private *dp) { - drm_dbg_dp(dp->drm_dev, "type=%d core_init=%d phy_init=%d\n", - dp->msm_dp_display.connector_type, dp->core_initialized, - dp->phy_initialized); + lockdep_assert_held(&dp->state_lock); + if (!dp->core_initialized) + return 0; + if (dp->msm_dp_display.audio_enabled || msm_dp_ctrl_has_phy_refs(dp->ctrl)) + return -EBUSY; - msm_dp_ctrl_reset(dp->ctrl, dp->panel); + msm_dp_ctrl_reset(dp->ctrl); msm_dp_ctrl_disable_irq(dp->ctrl); msm_dp_aux_deinit(dp->aux); msm_dp_ctrl_core_clk_disable(dp->ctrl); dp->core_initialized = false; + return 0; } static void msm_dp_display_handle_video_request(struct msm_dp_display_private *dp) @@ -389,29 +785,76 @@ static void msm_dp_display_handle_video_request(struct msm_dp_display_private *d static int msm_dp_display_handle_irq_hpd(struct msm_dp_display_private *dp) { u32 sink_request = dp->link->sink_request; + bool had_audio = dp->msm_dp_display.audio_enabled; + bool changes_link = sink_request & (DP_TEST_LINK_PHY_TEST_PATTERN | + DP_LINK_STATUS_UPDATED | DP_TEST_LINK_TRAINING); + int ret, cleanup; drm_dbg_dp(dp->drm_dev, "%d\n", sink_request); - msm_dp_ctrl_handle_sink_request(dp->ctrl, dp->panel); + lockdep_assert_held(&dp->state_lock); + + /* Compliance/retraining can stop clocks inside the controller call. */ + if (changes_link) { + ret = msm_dp_audio_stop(&dp->msm_dp_display); + if (ret) + goto retained; + } + ret = msm_dp_ctrl_handle_sink_request(dp->ctrl); + if (ret) { + cleanup = msm_dp_audio_stop(&dp->msm_dp_display); + if (cleanup) { + DRM_ERROR("Sink request failed: %d; audio stop retained: %d\n", + ret, cleanup); + goto retained; + } + dp->msm_dp_display.power_on = false; + msm_dp_display_aux_pending(dp); + msm_dp_aux_enable_xfers(dp->aux, false); + msm_dp_display_handle_plugged_change(&dp->msm_dp_display, false); + cleanup = msm_dp_ctrl_off(dp->ctrl); + if (!cleanup) + cleanup = msm_dp_display_host_phy_exit(dp); + if (cleanup) + DRM_ERROR("Sink request failed: %d; cleanup retained ownership: %d\n", + ret, cleanup); + /* Physical connection and existing PM-role leases are not stream success. */ + msm_dp_display_link_failed(dp, ret, true); + return ret; + } + if (changes_link && (sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) && + dp->link->phy_params.phy_test_pattern_sel) { + /* A PHY test pattern is not a normal video/audio stream. */ + msm_dp_display_aux_revoke(dp); + dp->msm_dp_display.power_on = false; + msm_dp_display_handle_plugged_change(&dp->msm_dp_display, false); + } else if (changes_link && had_audio) { + dp->audio->bw_code = drm_dp_link_rate_to_bw_code(dp->link->link_params.rate); + dp->audio->lane_count = dp->link->link_params.num_lanes; + ret = msm_dp_audio_restart(&dp->msm_dp_display); + if (ret) + goto retained; + } if (sink_request & DP_TEST_LINK_VIDEO_PATTERN) msm_dp_display_handle_video_request(dp); return 0; +retained: + dp->msm_dp_display.power_on = false; + msm_dp_display_aux_pending(dp); + msm_dp_aux_enable_xfers(dp->aux, false); + msm_dp_display_handle_plugged_change(&dp->msm_dp_display, false); + msm_dp_display_link_failed(dp, ret, true); + return ret; } static int msm_dp_hpd_plug_handle(struct msm_dp_display_private *dp) { - int ret; + int ret, cleanup; struct platform_device *pdev = dp->msm_dp_display.pdev; - bool was_plugged; - - drm_dbg_dp(dp->drm_dev, "Before, type=%d sink_count=%d\n", - dp->msm_dp_display.connector_type, - dp->link->sink_count); - - guard(mutex)(&dp->plugged_lock); - was_plugged = dp->plugged; + bool newly_owned = false, put_temporary = true, was_plugged; + unsigned int puts = 0; ret = pm_runtime_resume_and_get(&pdev->dev); if (ret) { @@ -419,62 +862,78 @@ static int msm_dp_hpd_plug_handle(struct msm_dp_display_private *dp) return ret; } - msm_dp_aux_enable_xfers(dp->aux, true); - - msm_dp_display_host_phy_init(dp); + mutex_lock(&dp->state_lock); + if (dp->link_status_stopping) { + ret = -ENODEV; + goto out; + } + was_plugged = dp->plugged; + dp->plugged = true; + ret = msm_dp_display_host_phy_init(dp, &newly_owned); + if (ret) + goto failed; ret = msm_dp_display_process_hpd_high(dp); + if (ret) + goto failed; if (!ret && dp->msm_dp_display.power_on && !was_plugged) - drm_connector_set_link_status_property(dp->msm_dp_display.connector, - DRM_MODE_LINK_STATUS_BAD); - - drm_dbg_dp(dp->drm_dev, "After, type=%d sink_count=%d\n", - dp->msm_dp_display.connector_type, - dp->link->sink_count); - - dp->plugged = true; + msm_dp_display_link_failed(dp, -EPIPE, false); + if (!dp->plugged_pm_held) { + dp->plugged_pm_held = true; + put_temporary = false; + } + goto out; + +failed: + msm_dp_display_link_failed(dp, ret, true); + if (newly_owned) { + msm_dp_aux_enable_xfers(dp->aux, false); + cleanup = msm_dp_display_host_phy_exit(dp); + if (cleanup) + DRM_ERROR("HPD PHY cleanup failed: %d (original %d)\n", cleanup, ret); + } + msm_dp_display_keep_cleanup_pm(dp, &put_temporary); +out: + msm_dp_display_release_cleanup_pm(dp, &puts); + mutex_unlock(&dp->state_lock); + msm_dp_display_put_pm(dp, puts + put_temporary); return ret; -}; - -static void msm_dp_display_handle_plugged_change(struct msm_dp *msm_dp_display, - bool plugged) -{ - struct msm_dp_display_private *dp; - - dp = container_of(msm_dp_display, - struct msm_dp_display_private, msm_dp_display); - - /* notify audio subsystem only if sink supports audio */ - if (dp->audio_supported) - drm_connector_hdmi_audio_plugged_notify(msm_dp_display->connector, - plugged); } static int msm_dp_hpd_unplug_handle(struct msm_dp_display_private *dp) { struct platform_device *pdev = dp->msm_dp_display.pdev; + unsigned int puts = 0; + int ret, cleanup = 0; + bool active; - dp->panel->video_test = false; + ret = pm_runtime_resume_and_get(&pdev->dev); + active = ret >= 0; + mutex_lock(&dp->state_lock); + dp->panel->video_test = false; + if (dp->msm_dp_display.is_edp) + msm_dp_display_aux_pending(dp); + else + msm_dp_display_aux_revoke(dp); msm_dp_aux_enable_xfers(dp->aux, false); - - drm_dbg_dp(dp->drm_dev, "Before, type=%d sink_count=%d\n", - dp->msm_dp_display.connector_type, - dp->link->sink_count); - - guard(mutex)(&dp->plugged_lock); - if (!dp->plugged) - return 0; + dp->plugged = false; + if (active) { + cleanup = msm_dp_audio_stop(&dp->msm_dp_display); + if (cleanup) + msm_dp_display_link_failed(dp, cleanup, false); + } /* Don't forget modes for eDP */ if (!dp->msm_dp_display.is_edp) - drm_edid_connector_update(dp->msm_dp_display.connector, NULL); + msm_dp_panel_unplugged(dp->panel, dp->msm_dp_display.connector); /* triggered by irq_hdp with sink_count = 0 */ - if (dp->link->sink_count == 0) - msm_dp_display_host_phy_exit(dp); + if (active && !cleanup && dp->link->sink_count == 0 && + !msm_dp_ctrl_has_power_ref(dp->ctrl)) + cleanup = msm_dp_display_host_phy_exit(dp); /* * We don't need separate work for disconnect as @@ -489,16 +948,13 @@ static int msm_dp_hpd_unplug_handle(struct msm_dp_display_private *dp) /* signal the disconnect event early to ensure proper teardown */ msm_dp_display_handle_plugged_change(&dp->msm_dp_display, false); - drm_dbg_dp(dp->drm_dev, "After, type=%d, sink_count=%d\n", - dp->msm_dp_display.connector_type, - dp->link->sink_count); - - if (dp->plugged) { - pm_runtime_put_sync(&pdev->dev); - dp->plugged = false; - } - - return 0; + msm_dp_display_release_pm_role(dp, &dp->plugged_pm_held, &puts); + msm_dp_display_release_cleanup_pm(dp, &puts); + mutex_unlock(&dp->state_lock); + msm_dp_display_put_pm(dp, puts + active); + if (cleanup) + DRM_ERROR("Disconnect retained PHY ownership: %d\n", cleanup); + return active ? cleanup : ret; } static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp) @@ -506,6 +962,17 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp) u32 sink_request; int rc = 0; + /* Both callers already hold a parent runtime-PM reference. */ + guard(mutex)(&dp->state_lock); + if (!dp->plugged) + return -ENOTCONN; + /* IRQ-HPD may be the first demand after a retained host-only resume. */ + rc = msm_dp_display_host_phy_init(dp, NULL); + if (rc) { + msm_dp_display_link_failed(dp, rc, true); + return rc; + } + /* irq_hpd can happen at either connected or disconnected state */ drm_dbg_dp(dp->drm_dev, "Before, type=%d, sink_count=%d\n", dp->msm_dp_display.connector_type, @@ -521,6 +988,8 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp) else rc = msm_dp_display_handle_irq_hpd(dp); } + if (rc) + msm_dp_display_link_failed(dp, rc, true); drm_dbg_dp(dp->drm_dev, "After, type=%d, sink_count=%d\n", dp->msm_dp_display.connector_type, @@ -532,6 +1001,7 @@ static int msm_dp_irq_hpd_handle(struct msm_dp_display_private *dp) static void msm_dp_display_deinit_sub_modules(struct msm_dp_display_private *dp) { msm_dp_audio_put(dp->audio); + msm_dp_panel_put(dp->panel); msm_dp_aux_put(dp->aux); } @@ -576,25 +1046,28 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) goto error_link; } - dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->aux, - phy, dp->ahb_base, dp->link_base); + dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->panel, dp->aux, + phy, &dp->state_lock, dp->ahb_base, dp->link_base); if (IS_ERR(dp->ctrl)) { rc = PTR_ERR(dp->ctrl); DRM_ERROR("failed to initialize ctrl, rc = %d\n", rc); dp->ctrl = NULL; - goto error_link; + goto error_ctrl; } - dp->audio = msm_dp_audio_get(dp->msm_dp_display.pdev, dp->link_base); + dp->audio = msm_dp_audio_get(dp->msm_dp_display.pdev, dp->link_base, + dp->ctrl, &dp->state_lock); if (IS_ERR(dp->audio)) { rc = PTR_ERR(dp->audio); pr_err("failed to initialize audio, rc = %d\n", rc); dp->audio = NULL; - goto error_link; + goto error_ctrl; } return rc; +error_ctrl: + msm_dp_panel_put(dp->panel); error_link: msm_dp_aux_put(dp->aux); error: @@ -602,64 +1075,20 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) } static int msm_dp_display_set_mode(struct msm_dp *msm_dp_display, - const struct drm_display_mode *adjusted_mode, - struct msm_dp_panel *msm_dp_panel) + struct msm_dp_display_mode *mode) { struct msm_dp_display_private *dp; - u32 bpp; dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - if (msm_dp_display_check_video_test(msm_dp_display)) - bpp = msm_dp_display_get_test_bpp(msm_dp_display); - else - bpp = msm_dp_panel->connector->display_info.bpc * 3; - - msm_dp_panel_init_panel_info(msm_dp_panel, adjusted_mode, bpp ? bpp : 24); - - /* populate wide_bus_support to different layers */ - dp->ctrl->wide_bus_en = - msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 ? false : dp->wide_bus_supported; + drm_mode_copy(&dp->panel->msm_dp_mode.drm_mode, &mode->drm_mode); + dp->panel->msm_dp_mode.bpp = mode->bpp; + dp->panel->msm_dp_mode.out_fmt_is_yuv_420 = mode->out_fmt_is_yuv_420; + msm_dp_panel_init_panel_info(dp->panel); return 0; } -static int msm_dp_display_prepare_link(struct msm_dp_display_private *dp) -{ - struct msm_dp *msm_dp_display = &dp->msm_dp_display; - int rc = 0; - bool force_link_train = false; - - drm_dbg_dp(dp->drm_dev, "sink_count=%d\n", dp->link->sink_count); - - if (msm_dp_display->is_edp) - msm_dp_hpd_plug_handle(dp); - - rc = pm_runtime_resume_and_get(&msm_dp_display->pdev->dev); - if (rc) { - DRM_ERROR("failed to pm_runtime_resume\n"); - return rc; - } - - if (dp->link->sink_count == 0) - return -ENOTCONN; - - if (!msm_dp_display->power_on) { - msm_dp_display_host_phy_init(dp); - force_link_train = true; - } - - rc = msm_dp_ctrl_on_link(dp->ctrl, dp->panel); - if (rc) { - DRM_ERROR("Failed link training (rc=%d)\n", rc); - // TODO: schedule drm_connector_set_link_status_property() - return rc; - } - - return msm_dp_ctrl_prepare_stream_on(dp->ctrl, dp->panel, force_link_train); -} - -static int msm_dp_display_enable(struct msm_dp_display_private *dp, - struct msm_dp_panel *msm_dp_panel) +static int msm_dp_display_enable(struct msm_dp_display_private *dp, bool force_link_train) { int rc = 0; struct msm_dp *msm_dp_display = &dp->msm_dp_display; @@ -670,7 +1099,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp, return 0; } - rc = msm_dp_ctrl_on_stream(dp->ctrl, msm_dp_panel); + rc = msm_dp_ctrl_on_stream(dp->ctrl, force_link_train); if (!rc) msm_dp_display->power_on = true; @@ -680,6 +1109,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp, static int msm_dp_display_post_enable(struct msm_dp *msm_dp_display) { struct msm_dp_display_private *dp; + int ret; u32 rate; dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); @@ -689,81 +1119,79 @@ static int msm_dp_display_post_enable(struct msm_dp *msm_dp_display) if (dp->audio_supported) { dp->audio->bw_code = drm_dp_link_rate_to_bw_code(rate); dp->audio->lane_count = dp->link->link_params.num_lanes; + ret = msm_dp_audio_restart(msm_dp_display); + if (ret) + return ret; } /* signal the connect event late to synchronize video and display */ msm_dp_display_handle_plugged_change(msm_dp_display, true); if (msm_dp_display->psr_supported) - msm_dp_ctrl_config_psr(dp->ctrl, dp->panel); + msm_dp_ctrl_config_psr(dp->ctrl); return 0; } -static void msm_dp_display_audio_notify_disable(struct msm_dp_display_private *dp) +static int msm_dp_display_disable(struct msm_dp_display_private *dp) { struct msm_dp *msm_dp_display = &dp->msm_dp_display; + int ret; - /* wait only if audio was enabled */ - if (msm_dp_display->audio_enabled) { - /* signal the disconnect event */ - msm_dp_display_handle_plugged_change(msm_dp_display, false); - if (!wait_for_completion_timeout(&dp->audio_comp, - HZ * 5)) - DRM_ERROR("audio comp timeout\n"); - } - - msm_dp_display->audio_enabled = false; -} - -static int msm_dp_display_disable(struct msm_dp_display_private *dp, - struct msm_dp_panel *msm_dp_panel) -{ - struct msm_dp *msm_dp_display = &dp->msm_dp_display; - - if (!msm_dp_display->power_on) - return 0; - - msm_dp_panel_disable_vsc_sdp(msm_dp_panel); - - msm_dp_ctrl_off_pixel_clk(dp->ctrl); - - /* dongle is still connected but sinks are disconnected */ - if (dp->link->sink_count == 0) - msm_dp_link_psm_config(dp->link, &msm_dp_panel->link_info, true); + lockdep_assert_held(&dp->state_lock); - msm_dp_ctrl_off_link(dp->ctrl, msm_dp_panel); + msm_dp_display_aux_pending(dp); + /* Disable the engine ourselves before clocks, never wait for ALSA under state_lock. */ + ret = msm_dp_audio_stop(msm_dp_display); + if (ret) { + msm_dp_display_link_failed(dp, ret, true); + return ret; + } + msm_dp_display_handle_plugged_change(msm_dp_display, false); - if (dp->link->sink_count == 0) - /* re-init the PHY so that we can listen to Dongle disconnect */ - msm_dp_ctrl_reinit_phy(dp->ctrl); - else - msm_dp_display_host_phy_exit(dp); + if (dp->link->sink_count == 0 && dp->plugged) { + /* + * irq_hpd with sink_count = 0 + * hdmi unplugged out of dongle + */ + ret = msm_dp_ctrl_off_link_stream(dp->ctrl); + } else { + /* + * unplugged interrupt + * dongle unplugged out of DUT + */ + ret = msm_dp_ctrl_off(dp->ctrl); + if (!ret) + ret = msm_dp_display_host_phy_exit(dp); + } msm_dp_display->power_on = false; + if (ret) + DRM_ERROR("Display cleanup retained ownership: %d\n", ret); drm_dbg_dp(dp->drm_dev, "sink count: %d\n", dp->link->sink_count); - return 0; + return ret; } /** - * msm_dp_display_mode_valid - callback to determine if specified mode is valid - * @dp: Pointer to dp display structure + * msm_dp_bridge_mode_valid - callback to determine if specified mode is valid + * @bridge: Pointer to drm bridge structure * @info: display info * @mode: Pointer to drm mode structure * Returns: Validity status for specified mode */ -enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, - const struct drm_display_info *info, - const struct drm_display_mode *mode) +enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) { const u32 num_components = 3, default_bpp = 24; struct msm_dp_display_private *msm_dp_display; struct msm_dp_link_info *link_info; u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; + struct msm_dp *dp; int mode_pclk_khz = mode->clock; - int link_pclk_khz; - bool is_yuv_420; + + dp = to_dp_bridge(bridge)->msm_dp_display; if (!dp || !mode_pclk_khz || !dp->connector) { DRM_ERROR("invalid params\n"); @@ -773,18 +1201,9 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); link_info = &msm_dp_display->panel->link_info; - is_yuv_420 = drm_mode_is_420_only(&dp->connector->display_info, mode); - - /* - * YUV 420 is carried over DP by signalling the colorimetry through a - * VSC SDP, so a 420-only mode cannot be driven without VSC SDP support. - */ - if (is_yuv_420 && !msm_dp_display->panel->vsc_sdp_supported) - return MODE_NO_420; - - link_pclk_khz = is_yuv_420 ? mode_pclk_khz / 2 : mode_pclk_khz; - - if (is_yuv_420 || msm_dp_display->wide_bus_supported) + if ((drm_mode_is_420_only(&dp->connector->display_info, mode) && + msm_dp_display->panel->vsc_sdp_supported) || + msm_dp_wide_bus_available(dp)) mode_pclk_khz /= 2; if (mode_pclk_khz > DP_MAX_PIXEL_CLK_KHZ) @@ -795,9 +1214,9 @@ enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, mode_bpp = default_bpp; mode_bpp = msm_dp_panel_get_mode_bpp(msm_dp_display->panel, - mode_bpp, link_pclk_khz); + mode_bpp, mode_pclk_khz); - mode_rate_khz = link_pclk_khz * mode_bpp; + mode_rate_khz = mode_pclk_khz * mode_bpp; supported_rate_khz = link_info->num_lanes * link_info->rate * 8; if (mode_rate_khz > supported_rate_khz) @@ -816,8 +1235,10 @@ int msm_dp_display_get_modes(struct msm_dp *dp) } msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); + guard(mutex)(&msm_dp_display->state_lock); - return drm_edid_connector_add_modes(msm_dp_display->panel->connector); + return msm_dp_panel_get_modes(msm_dp_display->panel, + dp->connector); } bool msm_dp_display_check_video_test(struct msm_dp *dp) @@ -851,14 +1272,16 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp) msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); /* - * if we are reading registers we need the link clocks to be on - * however till DP cable is connected this will not happen as we - * do not know the resolution to power up with. Hence check the - * power_on status before dumping DP registers to avoid crash due - * to unclocked access + * Do not wake an idle display for diagnostics. Hold an active parent + * reference and serialize the reads with link-clock/PHY teardown: a + * power_on check alone cannot protect the following register accesses. */ - if (!dp->power_on) + if (pm_runtime_get_if_active(&dp->pdev->dev) <= 0) return; + mutex_lock(&msm_dp_display->state_lock); + if (msm_dp_display->link_status_stopping || !msm_dp_display->core_initialized || + !dp->power_on || !msm_dp_ctrl_link_regs_accessible(msm_dp_display->ctrl)) + goto out; msm_disp_snapshot_add_block(disp_state, msm_dp_display->ahb_len, msm_dp_display->ahb_base, "dp_ahb"); @@ -868,11 +1291,15 @@ void msm_dp_snapshot(struct msm_disp_state *disp_state, struct msm_dp *dp) msm_dp_display->link_base, "dp_link"); msm_disp_snapshot_add_block(disp_state, msm_dp_display->p0_len, msm_dp_display->p0_base, "dp_p0"); +out: + mutex_unlock(&msm_dp_display->state_lock); + msm_dp_display_put_pm(msm_dp_display, 1); } void msm_dp_display_set_psr(struct msm_dp *msm_dp_display, bool enter) { struct msm_dp_display_private *dp; + int ret; if (!msm_dp_display) { DRM_ERROR("invalid params\n"); @@ -880,7 +1307,15 @@ void msm_dp_display_set_psr(struct msm_dp *msm_dp_display, bool enter) } dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - msm_dp_ctrl_set_psr(dp->ctrl, dp->panel, enter); + ret = pm_runtime_resume_and_get(&msm_dp_display->pdev->dev); + + if (ret < 0) + return; + mutex_lock(&dp->state_lock); + if (msm_dp_display->power_on) + msm_dp_ctrl_set_psr(dp->ctrl, enter); + mutex_unlock(&dp->state_lock); + msm_dp_display_put_pm(dp, 1); } /** @@ -901,23 +1336,30 @@ enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge, struct msm_dp_display_private *priv; u8 dpcd[DP_RECEIVER_CAP_SIZE]; struct drm_dp_desc desc; - bool phy_deinit; - int ret; + bool phy_deinit = false, put_temporary = true; + unsigned int puts = 0; + int ret, cleanup; dp = to_dp_bridge(bridge)->msm_dp_display; priv = container_of(dp, struct msm_dp_display_private, msm_dp_display); - guard(mutex)(&priv->plugged_lock); ret = pm_runtime_resume_and_get(&dp->pdev->dev); if (ret) { DRM_ERROR("failed to pm_runtime_resume\n"); return status; } - phy_deinit = msm_dp_display_host_phy_init(priv); + mutex_lock(&priv->state_lock); - msm_dp_aux_enable_xfers(priv->aux, true); + if (priv->link_status_stopping) + goto out; + ret = msm_dp_display_host_phy_init(priv, &phy_deinit); + if (ret) { + msm_dp_display_link_failed(priv, ret, false); + priv->plugged = false; + goto end; + } ret = msm_dp_aux_is_link_connected(priv->aux); DRM_DEBUG_DP("aux link status: %x\n", ret); @@ -961,12 +1403,20 @@ enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge, if (!priv->plugged) { if (phy_deinit) { msm_dp_aux_enable_xfers(priv->aux, false); - msm_dp_display_host_phy_exit(priv); + cleanup = msm_dp_display_host_phy_exit(priv); + if (cleanup) + DRM_ERROR("Detect PHY cleanup failed: %d\n", cleanup); } - - pm_runtime_put_sync(&dp->pdev->dev); + msm_dp_display_release_pm_role(priv, &priv->plugged_pm_held, &puts); + msm_dp_display_keep_cleanup_pm(priv, &put_temporary); + } else if (!priv->plugged_pm_held) { + priv->plugged_pm_held = true; + put_temporary = false; } - +out: + msm_dp_display_release_cleanup_pm(priv, &puts); + mutex_unlock(&priv->state_lock); + msm_dp_display_put_pm(priv, puts + put_temporary); return status; } @@ -990,7 +1440,7 @@ static irqreturn_t msm_dp_display_irq_handler(int irq, void *dev_id) } /* DP controller isr */ - ret |= msm_dp_ctrl_isr(dp->ctrl, dp->panel); + ret |= msm_dp_ctrl_isr(dp->ctrl); return ret; } @@ -1021,10 +1471,12 @@ static irqreturn_t msm_dp_display_irq_thread(int irq, void *dev_id) rc = pm_runtime_resume_and_get(&pdev->dev); if (rc) DRM_ERROR("failed to pm_runtime_resume\n"); - else - msm_dp_irq_hpd_handle(dp); - if (!rc) - pm_runtime_put_sync(&pdev->dev); + else { + rc = msm_dp_irq_hpd_handle(dp); + if (rc) + DRM_ERROR("HPD IRQ request failed: %d\n", rc); + msm_dp_display_put_pm(dp, 1); + } } ret = IRQ_HANDLED; @@ -1082,8 +1534,16 @@ static const struct msm_dp_desc *msm_dp_display_get_desc(struct platform_device static int msm_dp_display_probe_tail(struct device *dev) { struct msm_dp *dp = dev_get_drvdata(dev); + struct msm_dp_display_private *priv = container_of(dp, + struct msm_dp_display_private, msm_dp_display); int ret; + /* A deferred child must not add a component after final removal starts. */ + guard(mutex)(&priv->probe_lock); + if (priv->removing) + return -ENODEV; + if (priv->component_added) + return 0; /* * External bridges are mandatory for eDP interfaces: one has to * provide at least an eDP panel (which gets wrapped into panel-bridge). @@ -1102,6 +1562,8 @@ static int msm_dp_display_probe_tail(struct device *dev) ret = component_add(dev, &msm_dp_display_comp_ops); if (ret) DRM_ERROR("component add failed, rc=%d\n", ret); + else + priv->component_added = true; return ret; } @@ -1204,6 +1666,82 @@ static int msm_dp_display_get_io(struct msm_dp_display_private *display) return 0; } +static int msm_dp_display_fini(struct msm_dp_display_private *dp) +{ + struct device *dev = &dp->msm_dp_display.pdev->dev; + bool component_added, need_host, put_temporary = false; + unsigned int puts = 0; + int ret = 0, cleanup = 0; + + mutex_lock(&dp->probe_lock); + WRITE_ONCE(dp->removing, true); + component_added = dp->component_added; + dp->component_added = false; + mutex_unlock(&dp->probe_lock); + msm_dp_display_stop_link_status(dp); + if (component_added) + component_del(dev, &msm_dp_display_comp_ops); + if (dp->aux_bus_populated) { + of_dp_aux_depopulate_bus(dp->aux); + dp->aux_bus_populated = false; + } + if (dp->aux_registered) { + msm_dp_aux_unregister(dp->aux); + dp->aux_registered = false; + } + msm_dp_aux_owner_drain(dp->aux); + + mutex_lock(&dp->state_lock); + need_host = dp->core_initialized || msm_dp_ctrl_has_phy_refs(dp->ctrl) || + dp->msm_dp_display.audio_enabled; + mutex_unlock(&dp->state_lock); + if (dp->runtime_pm_enabled) { + if (need_host) { + ret = pm_runtime_resume_and_get(dev); + put_temporary = ret >= 0; + } + /* Wait for callbacks before freeing anything a callback can access. */ + pm_runtime_disable(dev); + dp->runtime_pm_enabled = false; + } + /* No waits on an HPD thread while state_lock is held. */ + if (dp->irq_requested) { + devm_free_irq(dev, dp->irq, dp); + dp->irq_requested = false; + dp->irq_enabled = false; + } + + mutex_lock(&dp->state_lock); + if (ret < 0) { + cleanup = ret; + } else if (dp->core_initialized) { + cleanup = msm_dp_display_drain_aux_fault(dp); + if (!cleanup) { + WARN_ON_ONCE(msm_dp_aux_finish_fault(dp->aux, true)); + msm_dp_aux_hpd_intr_disable(dp->aux); + msm_dp_aux_hpd_disable(dp->aux); + cleanup = msm_dp_display_host_deinit(dp); + } + } + msm_dp_display_release_pm_role(dp, &dp->plugged_pm_held, &puts); + msm_dp_display_release_pm_role(dp, &dp->stream_pm_held, &puts); + msm_dp_display_release_pm_role(dp, &dp->hpd_pm_held, &puts); + msm_dp_display_keep_cleanup_pm(dp, &put_temporary); + msm_dp_display_release_cleanup_pm(dp, &puts); + mutex_unlock(&dp->state_lock); + msm_dp_display_put_pm(dp, puts + put_temporary); + if (cleanup) { + /* A void platform remove cannot make failed physical cleanup safe. */ + dev_err(dev, "Final DP cleanup retained resources: %d\n", cleanup); + return cleanup; + } + ret = msm_dp_aux_owner_detach(dp->aux); + if (ret) + return ret; + dp->aux_owner_attached = false; + return 0; +} + static int msm_dp_display_probe(struct platform_device *pdev) { int rc = 0; @@ -1231,7 +1769,10 @@ static int msm_dp_display_probe(struct platform_device *pdev) (dp->msm_dp_display.connector_type == DRM_MODE_CONNECTOR_eDP); dp->hpd_isr_status = 0; - mutex_init(&dp->plugged_lock); + mutex_init(&dp->state_lock); + mutex_init(&dp->probe_lock); + INIT_WORK(&dp->link_status_work, msm_dp_display_link_status_work); + INIT_WORK(&dp->audio_notify_work, msm_dp_display_audio_notify_work); rc = msm_dp_display_get_io(dp); if (rc) @@ -1246,24 +1787,25 @@ static int msm_dp_display_probe(struct platform_device *pdev) /* Store DP audio handle inside DP display */ dp->msm_dp_display.msm_dp_audio = dp->audio; - init_completion(&dp->audio_comp); - platform_set_drvdata(pdev, &dp->msm_dp_display); - rc = devm_pm_runtime_enable(&pdev->dev); + rc = msm_dp_display_request_irq(dp); if (rc) goto err; - - rc = msm_dp_display_request_irq(dp); + dp->irq_requested = true; + rc = msm_dp_display_start_aux_owner(dp); if (rc) goto err; + pm_runtime_enable(&pdev->dev); + dp->runtime_pm_enabled = true; if (dp->msm_dp_display.is_edp) { - rc = devm_of_dp_aux_populate_bus(dp->aux, msm_dp_auxbus_done_probe); + rc = of_dp_aux_populate_bus(dp->aux, msm_dp_auxbus_done_probe); if (rc) { DRM_ERROR("eDP auxbus population failed, rc=%d\n", rc); goto err; } + dp->aux_bus_populated = true; } else { rc = msm_dp_display_probe_tail(&pdev->dev); if (rc) @@ -1273,7 +1815,10 @@ static int msm_dp_display_probe(struct platform_device *pdev) return rc; err: + if (WARN_ON(msm_dp_display_fini(dp))) + return rc; msm_dp_display_deinit_sub_modules(dp); + platform_set_drvdata(pdev, NULL); return rc; } @@ -1281,7 +1826,8 @@ static void msm_dp_display_remove(struct platform_device *pdev) { struct msm_dp_display_private *dp = dev_get_dp_display_private(&pdev->dev); - component_del(&pdev->dev, &msm_dp_display_comp_ops); + if (WARN_ON(msm_dp_display_fini(dp))) + return; msm_dp_display_deinit_sub_modules(dp); platform_set_drvdata(pdev, NULL); } @@ -1289,38 +1835,88 @@ static void msm_dp_display_remove(struct platform_device *pdev) static int msm_dp_pm_runtime_suspend(struct device *dev) { struct msm_dp_display_private *dp = dev_get_dp_display_private(dev); + bool aux_enabled; + int ret; - disable_irq(dp->irq); + mutex_lock(&dp->state_lock); - if (dp->msm_dp_display.is_edp) { - msm_dp_display_host_phy_exit(dp); - msm_dp_aux_hpd_disable(dp->aux); + /* Drain raw fault publication before deciding whether clocks can stop. */ + aux_enabled = msm_dp_aux_enable_xfers(dp->aux, false); + msm_dp_display_aux_pending(dp); + if (msm_dp_aux_get_fault(dp->aux) || msm_dp_aux_fault_pending(dp->aux) || + dp->cleanup_pm_held || dp->msm_dp_display.audio_enabled || + msm_dp_ctrl_has_power_ref(dp->ctrl)) { + mutex_unlock(&dp->state_lock); + return -EBUSY; } - msm_dp_display_host_deinit(dp); + if (!dp->core_initialized) { + mutex_unlock(&dp->state_lock); + return 0; + } + /* The hard handler never takes state_lock; never wait for the PM-using thread. */ + if (dp->irq_enabled && !disable_hardirq(dp->irq)) { + msm_dp_aux_enable_xfers(dp->aux, aux_enabled); + enable_irq(dp->irq); + mutex_unlock(&dp->state_lock); + return -EBUSY; + } + dp->irq_enabled = false; + ret = msm_dp_display_host_phy_exit(dp); + if (ret) + goto retained; + if (dp->msm_dp_display.is_edp) + msm_dp_aux_hpd_disable(dp->aux); + ret = msm_dp_display_host_deinit(dp); + if (ret) + goto retained; + mutex_unlock(&dp->state_lock); return 0; + +retained: + DRM_ERROR("Runtime suspend retained PHY/host resources: %d\n", ret); + enable_irq(dp->irq); + dp->irq_enabled = true; + mutex_unlock(&dp->state_lock); + /* A cleanup refusal must not install a sticky runtime-PM error. */ + return -EBUSY; } static int msm_dp_pm_runtime_resume(struct device *dev) { struct msm_dp_display_private *dp = dev_get_dp_display_private(dev); + int ret; /* - * for eDP, host cotroller, HPD block and PHY are enabled here - * but with HPD irq disabled - * - * for DP, only host controller is enabled here. - * HPD block is enabled at msm_dp_bridge_hpd_enable() - * PHY will be enabled at plugin handler later + * Resume only the host and HPD detector. PHY admission belongs to a real + * AUX/plug/stream demand after this callback, so a transient PHY error + * cannot poison runtime_error. Core clock errors remain real PM errors. + * A stopped owner still permits core-only child PM teardown; it cannot + * admit AUX or PHY work, and final removal disables PM before AUX is freed. */ - msm_dp_display_host_init(dp); - if (dp->msm_dp_display.is_edp) { + mutex_lock(&dp->state_lock); + ret = msm_dp_display_host_init(dp); + if (ret) + goto out; + if (dp->msm_dp_display.is_edp || dp->hpd_pm_held) { msm_dp_aux_hpd_enable(dp->aux); - msm_dp_display_host_phy_init(dp); + if (dp->hpd_pm_held) + msm_dp_aux_hpd_intr_enable(dp->aux); } - - enable_irq(dp->irq); + if (!dp->irq_enabled) { + enable_irq(dp->irq); + dp->irq_enabled = true; + } + mutex_unlock(&dp->state_lock); return 0; +out: + mutex_unlock(&dp->state_lock); + /* + * Keep IRQs disabled on a failed resume: genpd may stop and power down + * this device after any callback error. Such failures are quarantined; + * they do not promise automatic runtime-PM or PHY recovery. + */ + return ret; } static const struct dev_pm_ops msm_dp_pm_ops = { @@ -1358,10 +1954,22 @@ void __exit msm_dp_unregister(void) platform_driver_unregister(&msm_dp_display_driver); } +bool msm_dp_is_yuv_420_enabled(const struct msm_dp *msm_dp_display, + const struct drm_display_mode *mode) +{ + struct msm_dp_display_private *dp; + const struct drm_display_info *info; + + dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + info = &msm_dp_display->connector->display_info; + + return dp->panel->vsc_sdp_supported && drm_mode_is_420_only(info, mode); +} + bool msm_dp_needs_periph_flush(const struct msm_dp *msm_dp_display, const struct drm_display_mode *mode) { - return drm_mode_is_420_only(&msm_dp_display->connector->display_info, mode); + return msm_dp_is_yuv_420_enabled(msm_dp_display, mode); } bool msm_dp_wide_bus_available(const struct msm_dp *msm_dp_display) @@ -1370,7 +1978,7 @@ bool msm_dp_wide_bus_available(const struct msm_dp *msm_dp_display) dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); - if (dp->panel->msm_dp_mode.out_fmt_is_yuv_420) + if (dp->msm_dp_mode.out_fmt_is_yuv_420) return false; return dp->wide_bus_supported; @@ -1394,6 +2002,7 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev, struct drm_encoder *encoder, bool yuv_supported) { struct msm_dp_display_private *msm_dp_priv; + struct drm_connector *connector; int ret; msm_dp_display->drm_dev = dev; @@ -1407,122 +2016,228 @@ int msm_dp_modeset_init(struct msm_dp *msm_dp_display, struct drm_device *dev, return ret; } - msm_dp_display->connector = msm_dp_drm_connector_init(msm_dp_display, encoder); - if (IS_ERR(msm_dp_display->connector)) { - ret = PTR_ERR(msm_dp_display->connector); + connector = msm_dp_drm_connector_init(msm_dp_display, encoder); + + if (IS_ERR(connector)) { + ret = PTR_ERR(connector); DRM_DEV_ERROR(dev->dev, "failed to create dp connector: %d\n", ret); - msm_dp_display->connector = NULL; return ret; } - msm_dp_priv->panel->connector = msm_dp_display->connector; + mutex_lock(&msm_dp_priv->state_lock); + msm_dp_display->connector = connector; + msm_dp_priv->panel->connector = connector; + mutex_unlock(&msm_dp_priv->state_lock); return 0; } -void msm_dp_display_atomic_pre_enable(struct msm_dp *msm_dp_display, - struct drm_atomic_commit *state) +void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) { - struct msm_dp_display_private *dp; - struct drm_crtc *crtc; - struct drm_crtc_state *crtc_state; + struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = msm_dp_bridge->msm_dp_display; + int rc = 0; + struct msm_dp_display_private *msm_dp_display; - dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + bool force_link_train = false; + bool put_temporary = true; + unsigned int puts = 0; + int cleanup; + + msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - crtc = drm_atomic_get_new_crtc_for_encoder(state, msm_dp_display->bridge->encoder); - if (!crtc) + if (!msm_dp_display->msm_dp_mode.drm_mode.clock) { + DRM_ERROR("invalid params\n"); return; - crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + } - /* - * The DPU encoder's .atomic_enable() reads the mode's YUV420 / wide bus - * state and runs before the bridge's .atomic_enable(), so the mode must - * be programmed here, in .atomic_pre_enable(). - */ - msm_dp_display_set_mode(msm_dp_display, &crtc_state->adjusted_mode, dp->panel); -} + if (dp->is_edp) { + rc = msm_dp_hpd_plug_handle(msm_dp_display); + if (rc) + return; + } -void msm_dp_display_atomic_enable(struct msm_dp *msm_dp_display, - struct drm_atomic_commit *state) -{ - int rc = 0; - struct msm_dp_display_private *dp; + if (pm_runtime_resume_and_get(&dp->pdev->dev)) { + DRM_ERROR("failed to pm_runtime_resume\n"); + return; + } - dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + mutex_lock(&msm_dp_display->state_lock); + if (msm_dp_display->link_status_stopping) { + rc = -ENODEV; + goto failed; + } + if (msm_dp_display->link->sink_count == 0) { + rc = -ENOTCONN; + goto failed; + } - rc = msm_dp_display_prepare_link(dp); + rc = msm_dp_display_set_mode(dp, &msm_dp_display->msm_dp_mode); if (rc) { - DRM_ERROR("DP display prepare failed, rc=%d\n", rc); - return; + DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc); + goto failed; + } + + if (!dp->power_on) + force_link_train = true; + rc = msm_dp_audio_stop(dp); + if (rc) + goto failed; + rc = msm_dp_display_host_phy_init(msm_dp_display, NULL); + if (rc) + goto failed; + + rc = msm_dp_ctrl_on_link(msm_dp_display->ctrl, false); + if (rc) { + DRM_ERROR("Failed link training (rc=%d)\n", rc); + goto failed; } - rc = msm_dp_display_enable(dp, dp->panel); + rc = msm_dp_display_enable(msm_dp_display, force_link_train); if (rc) - DRM_ERROR("DP display enable failed, rc=%d\n", rc); + goto failed; - rc = msm_dp_display_post_enable(msm_dp_display); + rc = msm_dp_display_post_enable(dp); if (rc) { DRM_ERROR("DP display post enable failed, rc=%d\n", rc); - msm_dp_display_disable(dp, dp->panel); + goto failed; } - drm_dbg_dp(msm_dp_display->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); + msm_dp_display->link_error = 0; + msm_dp_display->link_status_bad = false; + msm_dp_display->link_status_notify = false; + if (!msm_dp_display->stream_pm_held) { + msm_dp_display->stream_pm_held = true; + put_temporary = false; + } + drm_dbg_dp(dp->drm_dev, "type=%d Done\n", dp->connector_type); + goto out; + +failed: + msm_dp_display_link_failed(msm_dp_display, rc, true); + cleanup = msm_dp_display_disable(msm_dp_display); + if (cleanup) + DRM_ERROR("Enable unwind failed: %d (original %d)\n", cleanup, rc); + msm_dp_display_release_pm_role(msm_dp_display, + &msm_dp_display->stream_pm_held, &puts); + msm_dp_display_keep_cleanup_pm(msm_dp_display, &put_temporary); +out: + msm_dp_display_release_cleanup_pm(msm_dp_display, &puts); + mutex_unlock(&msm_dp_display->state_lock); + msm_dp_display_put_pm(msm_dp_display, puts + put_temporary); } -void msm_dp_display_atomic_disable(struct msm_dp *dp) +void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) { + struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = msm_dp_bridge->msm_dp_display; struct msm_dp_display_private *msm_dp_display; + int ret; msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - /* - * If .atomic_enable() bailed out - link training failure is the common - * case - the mainlink was never brought up and ->power_on stayed false. - * Driving the PUSH_IDLE pattern into a controller that was never - * enabled times out, and .atomic_post_disable() then drops the - * controller's runtime-PM reference without tearing the PHY back down, - * because msm_dp_display_disable() returns early on !power_on. On - * glymur (Snapdragon X2 Elite) that combination is answered by a - * TrustZone-level SOCCP/ADSP force-stop and a silent SoC reset. - * There is nothing to push idle, so leave it alone. - */ - if (!dp->power_on) - return; + ret = pm_runtime_resume_and_get(&dp->pdev->dev); - msm_dp_ctrl_push_idle(msm_dp_display->ctrl); + if (ret < 0) + return; + mutex_lock(&msm_dp_display->state_lock); + ret = msm_dp_audio_stop(dp); + if (ret) + msm_dp_display_link_failed(msm_dp_display, ret, true); + else if (dp->power_on) + msm_dp_ctrl_push_idle(msm_dp_display->ctrl); + mutex_unlock(&msm_dp_display->state_lock); + msm_dp_display_put_pm(msm_dp_display, 1); } -static void msm_dp_display_unprepare(struct msm_dp_display_private *dp) +void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state) { - struct msm_dp *msm_dp_display = &dp->msm_dp_display; + struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = msm_dp_bridge->msm_dp_display; + struct msm_dp_display_private *msm_dp_display; + + unsigned int puts = 0; + bool put_temporary; + int ret; - pm_runtime_put_sync(&msm_dp_display->pdev->dev); + msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - drm_dbg_dp(dp->drm_dev, "type=%d Done\n", msm_dp_display->connector_type); + ret = pm_runtime_resume_and_get(&dp->pdev->dev); + put_temporary = ret >= 0; + mutex_lock(&msm_dp_display->state_lock); + if (put_temporary) + ret = msm_dp_display_disable(msm_dp_display); + else + dp->power_on = false; + if (ret < 0) + DRM_ERROR("Post-disable retained resources: %d\n", ret); + msm_dp_display_release_pm_role(msm_dp_display, + &msm_dp_display->stream_pm_held, &puts); + msm_dp_display_keep_cleanup_pm(msm_dp_display, &put_temporary); + msm_dp_display_release_cleanup_pm(msm_dp_display, &puts); + mutex_unlock(&msm_dp_display->state_lock); + + drm_dbg_dp(dp->drm_dev, "type=%d Done\n", dp->connector_type); + + msm_dp_display_put_pm(msm_dp_display, puts + put_temporary); + if (dp->is_edp) + msm_dp_hpd_unplug_handle(msm_dp_display); } -void msm_dp_display_atomic_post_disable(struct msm_dp *dp) +void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) { + struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(drm_bridge); + struct msm_dp *dp = msm_dp_bridge->msm_dp_display; struct msm_dp_display_private *msm_dp_display; + struct msm_dp_panel *msm_dp_panel; msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); + msm_dp_panel = msm_dp_display->panel; - if (dp->is_edp) - msm_dp_hpd_unplug_handle(msm_dp_display); + guard(mutex)(&msm_dp_display->state_lock); + + memset(&msm_dp_display->msm_dp_mode, 0x0, sizeof(struct msm_dp_display_mode)); + + if (msm_dp_display_check_video_test(dp)) + msm_dp_display->msm_dp_mode.bpp = msm_dp_display_get_test_bpp(dp); + else /* Default num_components per pixel = 3 */ + msm_dp_display->msm_dp_mode.bpp = dp->connector->display_info.bpc * 3; + + if (!msm_dp_display->msm_dp_mode.bpp) + msm_dp_display->msm_dp_mode.bpp = 24; /* Default bpp */ - msm_dp_display_audio_notify_disable(msm_dp_display); + drm_mode_copy(&msm_dp_display->msm_dp_mode.drm_mode, adjusted_mode); - msm_dp_display_disable(msm_dp_display, msm_dp_display->panel); + msm_dp_display->msm_dp_mode.v_active_low = + !!(msm_dp_display->msm_dp_mode.drm_mode.flags & DRM_MODE_FLAG_NVSYNC); - msm_dp_display_unprepare(msm_dp_display); + msm_dp_display->msm_dp_mode.h_active_low = + !!(msm_dp_display->msm_dp_mode.drm_mode.flags & DRM_MODE_FLAG_NHSYNC); + + msm_dp_display->msm_dp_mode.out_fmt_is_yuv_420 = + drm_mode_is_420_only(&dp->connector->display_info, adjusted_mode) && + msm_dp_panel->vsc_sdp_supported; + + /* populate wide_bus_support to different layers */ + msm_dp_display->ctrl->wide_bus_en = msm_dp_display->msm_dp_mode.out_fmt_is_yuv_420 ? + false : msm_dp_display->wide_bus_supported; } void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge) { struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(bridge); struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display; - struct msm_dp_display_private *dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + struct msm_dp_display_private *dp = container_of(msm_dp_display, + struct msm_dp_display_private, + msm_dp_display); + bool put_temporary = true; + unsigned int puts = 0; /* * this is for external DP with hpd irq enabled case, @@ -1537,20 +2252,40 @@ void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge) return; } - msm_dp_aux_hpd_enable(dp->aux); - msm_dp_aux_hpd_intr_enable(dp->aux); + mutex_lock(&dp->state_lock); + + if (!dp->hpd_pm_held && !dp->link_status_stopping) { + msm_dp_aux_hpd_enable(dp->aux); + msm_dp_aux_hpd_intr_enable(dp->aux); + dp->hpd_pm_held = true; + put_temporary = false; + } + msm_dp_display_release_cleanup_pm(dp, &puts); + mutex_unlock(&dp->state_lock); + msm_dp_display_put_pm(dp, puts + put_temporary); } void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge) { struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(bridge); struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display; - struct msm_dp_display_private *dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + struct msm_dp_display_private *dp = container_of(msm_dp_display, + struct msm_dp_display_private, + msm_dp_display); + unsigned int puts = 0; + int ret; - msm_dp_aux_hpd_intr_disable(dp->aux); - msm_dp_aux_hpd_disable(dp->aux); + ret = pm_runtime_resume_and_get(&msm_dp_display->pdev->dev); - pm_runtime_put_sync(&msm_dp_display->pdev->dev); + mutex_lock(&dp->state_lock); + if (ret >= 0 && dp->hpd_pm_held) { + msm_dp_aux_hpd_intr_disable(dp->aux); + msm_dp_aux_hpd_disable(dp->aux); + } + msm_dp_display_release_pm_role(dp, &dp->hpd_pm_held, &puts); + msm_dp_display_release_cleanup_pm(dp, &puts); + mutex_unlock(&dp->state_lock); + msm_dp_display_put_pm(dp, puts + (ret >= 0)); } void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, @@ -1560,15 +2295,25 @@ void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, { struct msm_dp_bridge *msm_dp_bridge = to_dp_bridge(bridge); struct msm_dp *msm_dp_display = msm_dp_bridge->msm_dp_display; - struct msm_dp_display_private *dp = container_of(msm_dp_display, struct msm_dp_display_private, msm_dp_display); + struct msm_dp_display_private *dp = container_of(msm_dp_display, + struct msm_dp_display_private, + msm_dp_display); u32 hpd_link_status = 0; + int ret; + + if (status != connector_status_connected) { + msm_dp_hpd_unplug_handle(dp); + return; + } if (pm_runtime_resume_and_get(&msm_dp_display->pdev->dev)) { DRM_ERROR("failed to pm_runtime_resume\n"); return; } + mutex_lock(&dp->state_lock); hpd_link_status = msm_dp_aux_is_link_connected(dp->aux); + mutex_unlock(&dp->state_lock); drm_dbg_dp(dp->drm_dev, "type=%d link hpd_link_status=0x%x, status=%d\n", msm_dp_display->connector_type, hpd_link_status, status); @@ -1576,16 +2321,18 @@ void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, if (status == connector_status_connected) { if (hpd_link_status == ISR_IRQ_HPD_PULSE_COUNT || extra_status == DRM_CONNECTOR_DP_IRQ_HPD) { - msm_dp_irq_hpd_handle(dp); + ret = msm_dp_irq_hpd_handle(dp); } else if (hpd_link_status == ISR_HPD_REPLUG_COUNT) { msm_dp_hpd_unplug_handle(dp); - msm_dp_hpd_plug_handle(dp); + ret = msm_dp_hpd_plug_handle(dp); } else { - msm_dp_hpd_plug_handle(dp); + ret = msm_dp_hpd_plug_handle(dp); } } else { - msm_dp_hpd_unplug_handle(dp); + ret = msm_dp_hpd_unplug_handle(dp); } + if (ret) + DRM_ERROR("HPD notification failed: %d\n", ret); - pm_runtime_put_sync(&msm_dp_display->pdev->dev); + msm_dp_display_put_pm(dp, 1); } diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h index 83117e7619f312..979343f038fa4a 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -29,18 +29,7 @@ struct msm_dp { int msm_dp_display_get_modes(struct msm_dp *msm_dp_display); bool msm_dp_display_check_video_test(struct msm_dp *msm_dp_display); int msm_dp_display_get_test_bpp(struct msm_dp *msm_dp_display); -void msm_dp_display_signal_audio_start(struct msm_dp *msm_dp_display); -void msm_dp_display_signal_audio_complete(struct msm_dp *msm_dp_display); void msm_dp_display_set_psr(struct msm_dp *dp, bool enter); void msm_dp_display_debugfs_init(struct msm_dp *msm_dp_display, struct dentry *dentry, bool is_edp); -void msm_dp_display_atomic_post_disable(struct msm_dp *dp_display); -void msm_dp_display_atomic_disable(struct msm_dp *dp_display); -void msm_dp_display_atomic_pre_enable(struct msm_dp *dp_display, - struct drm_atomic_commit *state); -void msm_dp_display_atomic_enable(struct msm_dp *dp_display, - struct drm_atomic_commit *state); -enum drm_mode_status msm_dp_display_mode_valid(struct msm_dp *dp, - const struct drm_display_info *info, - const struct drm_display_mode *mode); #endif /* _DP_DISPLAY_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c index 65303025ad093b..038c638ce9b01a 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.c +++ b/drivers/gpu/drm/msm/dp/dp_drm.c @@ -60,60 +60,14 @@ static int msm_dp_audio_startup(struct drm_bridge *bridge, return 0; } -static void msm_dp_bridge_atomic_pre_enable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) -{ - struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = dp_bridge->msm_dp_display; - - msm_dp_display_atomic_pre_enable(dp, state); -} - -static void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) -{ - struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = dp_bridge->msm_dp_display; - - msm_dp_display_atomic_enable(dp, state); -} - -static void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) -{ - struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = dp_bridge->msm_dp_display; - - msm_dp_display_atomic_disable(dp); -} - -static void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, - struct drm_atomic_commit *state) -{ - struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = dp_bridge->msm_dp_display; - - msm_dp_display_atomic_post_disable(dp); -} - -static enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *drm_bridge, - const struct drm_display_info *info, - const struct drm_display_mode *mode) -{ - struct msm_dp_bridge *dp_bridge = to_dp_bridge(drm_bridge); - struct msm_dp *dp = dp_bridge->msm_dp_display; - - return msm_dp_display_mode_valid(dp, info, mode); -} - static const struct drm_bridge_funcs msm_dp_bridge_ops = { .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state, - .atomic_create_state = drm_atomic_helper_bridge_create_state, - .atomic_pre_enable = msm_dp_bridge_atomic_pre_enable, + .atomic_create_state = drm_atomic_helper_bridge_create_state, .atomic_enable = msm_dp_bridge_atomic_enable, .atomic_disable = msm_dp_bridge_atomic_disable, .atomic_post_disable = msm_dp_bridge_atomic_post_disable, + .mode_set = msm_dp_bridge_mode_set, .mode_valid = msm_dp_bridge_mode_valid, .get_modes = msm_dp_bridge_get_modes, .detect = msm_dp_bridge_detect, @@ -174,7 +128,7 @@ static void msm_edp_bridge_atomic_enable(struct drm_bridge *drm_bridge, return; } - msm_dp_display_atomic_enable(dp, state); + msm_dp_bridge_atomic_enable(drm_bridge, state); } static void msm_edp_bridge_atomic_disable(struct drm_bridge *drm_bridge, @@ -288,10 +242,10 @@ static void msm_edp_bridge_debugfs_init(struct drm_bridge *bridge, struct dentry } static const struct drm_bridge_funcs msm_edp_bridge_ops = { - .atomic_pre_enable = msm_dp_bridge_atomic_pre_enable, .atomic_enable = msm_edp_bridge_atomic_enable, .atomic_disable = msm_edp_bridge_atomic_disable, .atomic_post_disable = msm_edp_bridge_atomic_post_disable, + .mode_set = msm_dp_bridge_mode_set, .mode_valid = msm_edp_bridge_mode_valid, .atomic_create_state = drm_atomic_helper_bridge_create_state, .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h index f6b96c27408afb..4d98e04bb51136 100644 --- a/drivers/gpu/drm/msm/dp/dp_drm.h +++ b/drivers/gpu/drm/msm/dp/dp_drm.h @@ -27,6 +27,18 @@ int msm_dp_bridge_init(struct msm_dp *msm_dp_display, struct drm_device *dev, enum drm_connector_status msm_dp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector); +void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state); +void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state); +void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, + struct drm_atomic_commit *state); +enum drm_mode_status msm_dp_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode); +void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode); void msm_dp_bridge_hpd_enable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_disable(struct drm_bridge *bridge); void msm_dp_bridge_hpd_notify(struct drm_bridge *bridge, diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c index e4906781370719..ec7e9a970dcb99 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.c +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -114,10 +114,6 @@ static int msm_dp_panel_read_dpcd(struct msm_dp_panel *msm_dp_panel) msm_dp_panel->vsc_sdp_supported = drm_dp_vsc_sdp_supported(panel->aux, dpcd); link_info = &msm_dp_panel->link_info; - - /* Drop the capabilities of the previous sink, they are recomputed below */ - memset(link_info, 0, sizeof(*link_info)); - link_info->revision = dpcd[DP_DPCD_REV]; major = (link_info->revision >> 4) & 0x0f; minor = link_info->revision & 0x0f; @@ -249,8 +245,8 @@ static u32 msm_dp_panel_get_supported_bpp(struct msm_dp_panel *msm_dp_panel, return min_supported_bpp; } -int msm_dp_panel_read_link_caps(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector) +int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel, + struct drm_connector *connector) { int rc, bw_code; int count; @@ -288,9 +284,36 @@ int msm_dp_panel_read_link_caps(struct msm_dp_panel *msm_dp_panel, rc = drm_dp_read_downstream_info(panel->aux, msm_dp_panel->dpcd, msm_dp_panel->downstream_ports); + if (rc) + return rc; + + drm_edid_free(msm_dp_panel->drm_edid); + + msm_dp_panel->drm_edid = drm_edid_read_ddc(connector, &panel->aux->ddc); + + drm_edid_connector_update(connector, msm_dp_panel->drm_edid); + + if (!msm_dp_panel->drm_edid) { + DRM_ERROR("panel edid read failed\n"); + /* check edid read fail is due to unplug */ + if (!msm_dp_aux_is_link_connected(panel->aux)) { + rc = -ETIMEDOUT; + goto end; + } + } + +end: return rc; } +void msm_dp_panel_unplugged(struct msm_dp_panel *msm_dp_panel, + struct drm_connector *connector) +{ + drm_edid_connector_update(connector, NULL); + drm_edid_free(msm_dp_panel->drm_edid); + msm_dp_panel->drm_edid = NULL; +} + u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel, u32 mode_edid_bpp, u32 mode_pclk_khz) { @@ -314,6 +337,22 @@ u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel, return bpp; } +int msm_dp_panel_get_modes(struct msm_dp_panel *msm_dp_panel, + struct drm_connector *connector) +{ + if (!msm_dp_panel) { + DRM_ERROR("invalid input\n"); + return -EINVAL; + } + + if (msm_dp_panel->drm_edid) { + drm_edid_connector_update(connector, msm_dp_panel->drm_edid); + return drm_edid_connector_add_modes(connector); + } + + return 0; +} + static u8 msm_dp_panel_get_edid_checksum(const struct edid *edid) { edid += edid->extensions; @@ -321,8 +360,7 @@ static u8 msm_dp_panel_get_edid_checksum(const struct edid *edid) return edid->checksum; } -void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel, - const struct drm_edid *drm_edid) +void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel) { struct msm_dp_panel_private *panel; @@ -335,7 +373,7 @@ void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel, if (panel->link->sink_request & DP_TEST_LINK_EDID_READ) { /* FIXME: get rid of drm_edid_raw() */ - const struct edid *edid = drm_edid_raw(drm_edid); + const struct edid *edid = drm_edid_raw(msm_dp_panel->drm_edid); u8 checksum; if (edid) @@ -664,27 +702,15 @@ int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en) return 0; } -int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel, - const struct drm_display_mode *adjusted_mode, - u32 bpp) +int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel) { struct drm_display_mode *drm_mode; struct msm_dp_panel_private *panel; - panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel); - - drm_mode_copy(&msm_dp_panel->msm_dp_mode.drm_mode, adjusted_mode); - msm_dp_panel->msm_dp_mode.bpp = bpp; - msm_dp_panel->msm_dp_mode.v_active_low = - !!(adjusted_mode->flags & DRM_MODE_FLAG_NVSYNC); - msm_dp_panel->msm_dp_mode.h_active_low = - !!(adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC); - msm_dp_panel->msm_dp_mode.out_fmt_is_yuv_420 = - drm_mode_is_420_only(&msm_dp_panel->connector->display_info, adjusted_mode) && - msm_dp_panel->vsc_sdp_supported; - drm_mode = &msm_dp_panel->msm_dp_mode.drm_mode; + panel = container_of(msm_dp_panel, struct msm_dp_panel_private, msm_dp_panel); + /* * print resolution info as this is a result * of user initiated action of cable connection @@ -706,7 +732,8 @@ int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel, drm_mode->clock); drm_dbg_dp(panel->drm_dev, "bpp = %d\n", msm_dp_panel->msm_dp_mode.bpp); - msm_dp_panel->msm_dp_mode.bpp = msm_dp_panel_get_mode_bpp(msm_dp_panel, msm_dp_panel->msm_dp_mode.bpp, + msm_dp_panel->msm_dp_mode.bpp = + msm_dp_panel_get_mode_bpp(msm_dp_panel, msm_dp_panel->msm_dp_mode.bpp, msm_dp_panel->msm_dp_mode.drm_mode.clock); drm_dbg_dp(panel->drm_dev, "updated bpp = %d\n", @@ -744,3 +771,10 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux return msm_dp_panel; } +void msm_dp_panel_put(struct msm_dp_panel *msm_dp_panel) +{ + if (!msm_dp_panel) + return; + + drm_edid_free(msm_dp_panel->drm_edid); +} diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h index 4519ac37422064..9173e90a505309 100644 --- a/drivers/gpu/drm/msm/dp/dp_panel.h +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -33,6 +33,7 @@ struct msm_dp_panel { u8 downstream_ports[DP_MAX_DOWNSTREAM_PORTS]; struct msm_dp_link_info link_info; + const struct drm_edid *drm_edid; struct drm_connector *connector; struct msm_dp_display_mode msm_dp_mode; struct msm_dp_panel_psr psr_cap; @@ -43,17 +44,18 @@ struct msm_dp_panel { u32 max_bw_code; }; -int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel, - const struct drm_display_mode *adjusted_mode, - u32 bpp); +int msm_dp_panel_init_panel_info(struct msm_dp_panel *msm_dp_panel); int msm_dp_panel_deinit(struct msm_dp_panel *msm_dp_panel); int msm_dp_panel_timing_cfg(struct msm_dp_panel *msm_dp_panel, bool wide_bus_en); -int msm_dp_panel_read_link_caps(struct msm_dp_panel *msm_dp_panel, - struct drm_connector *connector); +int msm_dp_panel_read_sink_caps(struct msm_dp_panel *msm_dp_panel, + struct drm_connector *connector); +void msm_dp_panel_unplugged(struct msm_dp_panel *msm_dp_panel, + struct drm_connector *connector); u32 msm_dp_panel_get_mode_bpp(struct msm_dp_panel *msm_dp_panel, u32 mode_max_bpp, u32 mode_pclk_khz); -void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel, - const struct drm_edid *drm_edid); +int msm_dp_panel_get_modes(struct msm_dp_panel *msm_dp_panel, + struct drm_connector *connector); +void msm_dp_panel_handle_sink_request(struct msm_dp_panel *msm_dp_panel); void msm_dp_panel_tpg_config(struct msm_dp_panel *msm_dp_panel, bool enable); void msm_dp_panel_clear_dsc_dto(struct msm_dp_panel *msm_dp_panel); @@ -92,4 +94,5 @@ struct msm_dp_panel *msm_dp_panel_get(struct device *dev, struct drm_dp_aux *aux struct msm_dp_link *link, void __iomem *link_base, void __iomem *p0_base); +void msm_dp_panel_put(struct msm_dp_panel *msm_dp_panel); #endif /* _DP_PANEL_H_ */ From 0ee23386a8ac21d5c941ed85f0f766597ed0fd07 Mon Sep 17 00:00:00 2001 From: Leon Silcott Date: Sat, 14 Nov 2026 05:36:32 +0000 Subject: [PATCH 2/2] drm/msm/dp: config_ctrl: set LSCLK divider after the RMW read The 2 << LSCLK_DIV_SHIFT default written before the read-modify-write was a dead store: the RMW immediately overwrote it, so the link-level divider field kept whatever the register held instead of the intended 1/4 LCLK default. Move the default assignment after the RMW so the divider is explicitly re-set on every configure, matching the intent of the split in 7de83c21291f (found in the dp-cluster port review). Built on Surface Pro 11 (arm64 gcc 15.2, 7.3.0-jg-1sp11v4 headers), zero warnings. Signed-off-by: Leon Silcott --- drivers/gpu/drm/msm/dp/dp_ctrl.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c index f6465774f4a84b..ac1dec25464310 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.c +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -512,12 +512,9 @@ void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl) static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl) { - u32 config = 0, tbd; + u32 config, tbd; const u8 *dpcd = ctrl->panel->dpcd; - /* Default-> LSCLK DIV: 1/4 LCLK */ - config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT); - /* * RMW: serialized by the DRM atomic framework. Clear stream-specific * fields before OR-ing to avoid bit accumulation across re-enables. @@ -527,6 +524,9 @@ static void msm_dp_ctrl_config_ctrl(struct msm_dp_ctrl_private *ctrl) DP_CONFIGURATION_CTRL_BPC_MASK | DP_CONFIGURATION_CTRL_SEND_VSC); + /* Default-> LSCLK DIV: 1/4 LCLK */ + config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT); + if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420) config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */