From 507356d03f3c0984aa8110fbc3cf7c7d52a33ed9 Mon Sep 17 00:00:00 2001 From: Leon Silcott Date: Tue, 20 Oct 2026 19:46:04 +0100 Subject: [PATCH] feat(qcom): preserve experimental Type-C display recovery source Record the fourteen frozen recovery driver and header inputs on the accepted v26 source. Keep reset, QMP, PMIC GLINK ingress and altmode, PS883x, and MSM DP recovery work available for focused review. This source-only tracking commit is not the v27 producer identity, a new full-tree validation, operational USB4 support, or the deferred GLINK channel lifecycle repair. No board activation or packaging change is included. Signed-off-by: Leon Silcott --- drivers/clk/qcom/reset.c | 18 +- drivers/gpu/drm/msm/dp/dp_audio.c | 163 ++- drivers/gpu/drm/msm/dp/dp_audio.h | 19 +- drivers/gpu/drm/msm/dp/dp_aux.c | 288 ++++- drivers/gpu/drm/msm/dp/dp_aux.h | 31 +- drivers/gpu/drm/msm/dp/dp_ctrl.c | 681 +++++++++--- drivers/gpu/drm/msm/dp/dp_ctrl.h | 24 +- drivers/gpu/drm/msm/dp/dp_display.c | 1178 +++++++++++++++++---- drivers/gpu/drm/msm/dp/dp_display.h | 2 - drivers/phy/qualcomm/phy-qcom-qmp-combo.c | 1049 +++++++++++++----- drivers/soc/qcom/pmic_glink.c | 66 +- drivers/soc/qcom/pmic_glink_altmode.c | 506 ++++++--- drivers/usb/typec/mux/ps883x.c | 33 +- include/linux/soc/qcom/pmic_glink.h | 3 + 14 files changed, 3237 insertions(+), 824 deletions(-) diff --git a/drivers/clk/qcom/reset.c b/drivers/clk/qcom/reset.c index d96c96a9089f40..7b6c478e2a2066 100644 --- a/drivers/clk/qcom/reset.c +++ b/drivers/clk/qcom/reset.c @@ -14,12 +14,15 @@ static int qcom_reset(struct reset_controller_dev *rcdev, unsigned long id) { struct qcom_reset_controller *rst = to_qcom_reset_controller(rcdev); + int ret; + + ret = rcdev->ops->assert(rcdev, id); + if (ret) + return ret; - rcdev->ops->assert(rcdev, id); fsleep(rst->reset_map[id].udelay ?: 1); /* use 1 us as default */ - rcdev->ops->deassert(rcdev, id); - return 0; + return rcdev->ops->deassert(rcdev, id); } static int qcom_reset_set_assert(struct reset_controller_dev *rcdev, @@ -28,17 +31,18 @@ static int qcom_reset_set_assert(struct reset_controller_dev *rcdev, struct qcom_reset_controller *rst; const struct qcom_reset_map *map; u32 mask; + int ret; rst = to_qcom_reset_controller(rcdev); map = &rst->reset_map[id]; mask = map->bitmask ? map->bitmask : BIT(map->bit); - regmap_update_bits(rst->regmap, map->reg, mask, assert ? mask : 0); + ret = regmap_update_bits(rst->regmap, map->reg, mask, assert ? mask : 0); + if (ret) + return ret; /* Read back the register to ensure write completion, ignore the value */ - regmap_read(rst->regmap, map->reg, &mask); - - return 0; + return regmap_read(rst->regmap, map->reg, &mask); } static int qcom_reset_assert(struct reset_controller_dev *rcdev, unsigned long id) diff --git a/drivers/gpu/drm/msm/dp/dp_audio.c b/drivers/gpu/drm/msm/dp/dp_audio.c index af7b72dd896c7a..eda6b0260d916d 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,122 @@ 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 +396,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 +405,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 +448,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..f5d4530bfb85f6 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,8 @@ 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 +427,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 +504,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 +549,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 +613,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 +936,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 61f2a9688b72e2..dafe96d33a668a 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 @@ -121,6 +122,13 @@ struct msm_dp_ctrl_private { 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; @@ -144,6 +152,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); @@ -210,6 +320,7 @@ void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_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; @@ -259,6 +370,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, @@ -270,6 +382,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); } @@ -362,6 +475,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, @@ -371,7 +487,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) @@ -380,6 +496,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); @@ -1292,29 +1411,34 @@ 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, @@ -1370,7 +1494,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, @@ -1402,6 +1526,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); @@ -1430,6 +1558,9 @@ static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl, delay_us = drm_dp_read_clock_recovery_delay(ctrl->aux, 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); @@ -1440,6 +1571,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); @@ -1452,6 +1586,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; @@ -1535,10 +1672,17 @@ static void msm_dp_ctrl_clear_training_pattern(struct msm_dp_ctrl_private *ctrl, { 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, ctrl->panel->dpcd, dp_phy, false); + if (msm_dp_ctrl_phy_status(ctrl)) + return; fsleep(delay_us); } @@ -1554,6 +1698,9 @@ static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl, delay_us = drm_dp_read_channel_eq_delay(ctrl->aux, 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); @@ -1575,11 +1722,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; @@ -1630,6 +1783,10 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, u8 assr; struct msm_dp_link_info link_info = {0}; + 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; @@ -1637,24 +1794,35 @@ static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl, 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, training_step, dp_phy); - msm_dp_ctrl_clear_training_pattern(ctrl, 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; @@ -1674,13 +1842,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, 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); @@ -1705,6 +1877,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; @@ -1731,6 +1904,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; @@ -1749,6 +1929,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; @@ -1757,7 +1938,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); @@ -1781,6 +1964,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; @@ -1792,34 +1979,65 @@ static void msm_dp_ctrl_link_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl) str_on_off(ctrl->core_clks_on)); } -static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl) +static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl, + bool restore_xfers) { - int ret = 0; - struct phy *phy = ctrl->phy; 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; } @@ -1862,6 +2080,10 @@ void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl) struct msm_dp_ctrl_private, msm_dp_ctrl); u32 cfg; + 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; @@ -1884,6 +2106,7 @@ 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, bool enter) @@ -1891,6 +2114,10 @@ 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); + 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; @@ -1937,38 +2164,145 @@ 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; +} + +bool msm_dp_ctrl_has_phy_refs(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_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); } static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) { - struct phy *phy = ctrl->phy; - int ret = 0; + bool xfers; + int ret; - msm_dp_ctrl_mainlink_disable(ctrl); + 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 @@ -1977,11 +2311,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); + ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl, xfers); if (ret) { DRM_ERROR("Failed to enable mainlink clks. ret=%d\n", ret); return ret; @@ -1992,23 +2328,25 @@ static int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl) 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); - - msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); - - phy_power_off(phy); - - /* aux channel down, reinit phy */ - phy_exit(phy); - phy_init(phy); + bool xfers, had_init; + int ret; - return 0; + lockdep_assert_held(ctrl->state_lock); + 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) @@ -2016,6 +2354,10 @@ 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; @@ -2026,6 +2368,9 @@ static int msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl) goto end; 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); @@ -2117,23 +2462,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); @@ -2166,14 +2518,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_process_phy_test_request(struct msm_dp_ctrl_private *ctrl) { int ret; + int cleanup; + bool xfers; unsigned long pixel_rate; + ret = msm_dp_ctrl_phy_status(ctrl); + if (ret) + return ret; + if (!ctrl->link->phy_params.phy_test_pattern_sel) { drm_dbg_dp(ctrl->drm_dev, "no test pattern selected by sink\n"); @@ -2185,19 +2543,21 @@ 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(&ctrl->msm_dp_ctrl); + 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); + 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); - return ret; + goto err_stop; } if (ctrl->stream_clks_on) { @@ -2206,27 +2566,42 @@ static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl ret = clk_prepare_enable(ctrl->pixel_clk); if (ret) { DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret); - return ret; + goto err_stop; } ctrl->stream_clks_on = true; } - msm_dp_ctrl_send_phy_test_pattern(ctrl); - - 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) +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 | @@ -2237,32 +2612,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)) { + 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)) { + 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)) { + 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( @@ -2288,13 +2667,18 @@ 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; - drm_dp_dpcd_read_link_status(ctrl->aux, link_status); + if (msm_dp_ctrl_phy_status(ctrl)) + return false; + 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) +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; @@ -2304,16 +2688,26 @@ 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 = 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, @@ -2332,13 +2726,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); + 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, &training_step); + if (msm_dp_ctrl_phy_status(ctrl)) + break; if (rc == 0) { /* training completed successfully */ break; @@ -2348,6 +2744,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 */ @@ -2373,6 +2772,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; if (!drm_dp_clock_recovery_ok(link_status, ctrl->link->link_params.num_lanes)) @@ -2387,6 +2789,9 @@ 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, DP_PHY_DPRX); + rc = msm_dp_ctrl_phy_status(ctrl); + if (rc) + break; } rc = msm_dp_ctrl_reinitialize_mainlink(ctrl); @@ -2396,6 +2801,7 @@ int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl) } } + rc = msm_dp_ctrl_phy_status(ctrl) ?: rc; if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) return rc; @@ -2410,10 +2816,16 @@ 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, DP_PHY_DPRX); + if (!msm_dp_ctrl_phy_status(ctrl)) + msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX); - msm_dp_ctrl_deinitialize_mainlink(ctrl); - rc = -ECONNRESET; + 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; @@ -2498,14 +2910,26 @@ 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); + 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; @@ -2521,7 +2945,7 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train 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); + ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl, false); if (ret) { DRM_ERROR("Failed to start link clocks. ret=%d\n", ret); goto end; @@ -2545,11 +2969,20 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train ctrl->stream_clks_on = true; } - if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl)) - msm_dp_ctrl_link_retrain(ctrl); + 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; + } /* 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) + goto end; /* * Set up transfer unit values and set controller state to send @@ -2572,70 +3005,72 @@ int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train 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_off_link_stream(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; - - msm_dp_panel_disable_vsc_sdp(ctrl->panel); + lockdep_assert_held(ctrl->state_lock); + had_init = ctrl->phy_init_owned; - /* set dongle to D3 (power off) mode */ - msm_dp_link_psm_config(ctrl->link, &ctrl->panel->link_info, true); - - msm_dp_ctrl_mainlink_disable(ctrl); - - if (ctrl->stream_clks_on) { - clk_disable_unprepare(ctrl->pixel_clk); - ctrl->stream_clks_on = false; + /* 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); } - - msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl); - - phy_power_off(phy); - - /* aux channel down, reinit phy */ - phy_exit(phy); - phy_init(phy); + 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(struct msm_dp_ctrl *msm_dp_ctrl) +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(ctrl->panel); - - msm_dp_ctrl_mainlink_disable(ctrl); - - 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); - - 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) @@ -2740,13 +3175,14 @@ 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 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 || !panel || !aux || !link) { + if (!dev || !panel || !aux || !link || !state_lock) { DRM_ERROR("invalid input\n"); return ERR_PTR(-EINVAL); } @@ -2779,6 +3215,7 @@ struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link 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 f68bee62713f16..ec7916c280807e 100644 --- a/drivers/gpu/drm/msm/dp/dp_ctrl.h +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -15,25 +15,37 @@ struct msm_dp_ctrl { }; struct phy; +struct mutex; -int msm_dp_ctrl_on_link(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); -void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl); -void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl); +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); -void msm_dp_ctrl_handle_sink_request(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); -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); +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, bool enable); diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c index 754ec79cac9d72..7dd15494bc41c1 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,36 @@ 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; @@ -66,9 +95,6 @@ struct msm_dp_display_private { 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; @@ -96,6 +122,190 @@ 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 }, @@ -185,22 +395,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, @@ -211,19 +417,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; } @@ -233,12 +448,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 = { @@ -305,66 +519,252 @@ static int msm_dp_display_process_hpd_high(struct msm_dp_display_private *dp) 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) +{ + lockdep_assert_held(&dp->state_lock); + dp->aux_demand_enabled = false; + dp->aux_restore_pending = false; + msm_dp_aux_enable_xfers(dp->aux, false); +} + +/* 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) { - 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; - if (!dp->phy_initialized) { - msm_dp_ctrl_phy_init(dp->ctrl); - dp->phy_initialized = true; - return true; + 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; +} + +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 int msm_dp_display_raw_aux_admit(void *data, bool *put_temporary) +{ + struct msm_dp_display_private *dp = data; + unsigned int puts = 0; + int ret; + + /* 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; +} - return false; +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 void msm_dp_display_host_phy_exit(struct msm_dp_display_private *dp) +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) { - 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); + unsigned int puts = 0; + bool put_temporary = false; + int ret; - if (dp->phy_initialized) { - msm_dp_ctrl_phy_exit(dp->ctrl); - dp->phy_initialized = false; + 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; - msm_dp_ctrl_core_clk_enable(dp->ctrl); + lockdep_assert_held(&dp->state_lock); + 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); 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) @@ -378,29 +778,75 @@ 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); + 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) { @@ -408,62 +854,76 @@ 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; + 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) 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 @@ -478,16 +938,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) @@ -495,6 +952,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, @@ -510,6 +978,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, @@ -567,7 +1037,7 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) } dp->ctrl = msm_dp_ctrl_get(dev, dp->link, dp->panel, dp->aux, - phy, dp->ahb_base, dp->link_base); + 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); @@ -575,7 +1045,8 @@ static int msm_dp_init_sub_modules(struct msm_dp_display_private *dp) 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); @@ -628,6 +1099,7 @@ static int msm_dp_display_enable(struct msm_dp_display_private *dp, bool force_l 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); @@ -637,6 +1109,9 @@ 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 */ @@ -651,40 +1126,41 @@ static int msm_dp_display_post_enable(struct msm_dp *msm_dp_display) static int msm_dp_display_disable(struct msm_dp_display_private *dp) { struct msm_dp *msm_dp_display = &dp->msm_dp_display; + int ret; - if (!msm_dp_display->power_on) - return 0; + lockdep_assert_held(&dp->state_lock); - /* 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_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); - msm_dp_display->audio_enabled = false; - - if (dp->link->sink_count == 0) { + if (dp->link->sink_count == 0 && dp->plugged) { /* * irq_hpd with sink_count = 0 * hdmi unplugged out of dongle */ - msm_dp_ctrl_off_link_stream(dp->ctrl); + ret = msm_dp_ctrl_off_link_stream(dp->ctrl); } else { /* * unplugged interrupt * dongle unplugged out of DUT */ - msm_dp_ctrl_off(dp->ctrl); - msm_dp_display_host_phy_exit(dp); + 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; } /** @@ -749,6 +1225,7 @@ 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 msm_dp_panel_get_modes(msm_dp_display->panel, dp->connector); @@ -785,14 +1262,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"); @@ -802,11 +1281,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"); @@ -814,7 +1297,14 @@ 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, 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); } /** @@ -835,23 +1325,29 @@ 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); - - msm_dp_aux_enable_xfers(priv->aux, true); + mutex_lock(&priv->state_lock); + 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); @@ -895,12 +1391,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; } @@ -955,10 +1459,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; @@ -1016,8 +1522,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). @@ -1036,6 +1550,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; } @@ -1138,6 +1654,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; @@ -1165,7 +1757,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) @@ -1180,24 +1775,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) @@ -1207,7 +1803,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; } @@ -1215,7 +1814,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); } @@ -1223,38 +1823,87 @@ 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); - - if (dp->msm_dp_display.is_edp) { - msm_dp_display_host_phy_exit(dp); - msm_dp_aux_hpd_disable(dp->aux); + mutex_lock(&dp->state_lock); + /* 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; + } + 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; } - msm_dp_display_host_deinit(dp); + 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 = { @@ -1340,6 +1989,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; @@ -1353,16 +2003,18 @@ 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; } @@ -1375,6 +2027,9 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, int rc = 0; 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); if (!msm_dp_display->msm_dp_mode.drm_mode.clock) { @@ -1382,44 +2037,80 @@ void msm_dp_bridge_atomic_enable(struct drm_bridge *drm_bridge, return; } - if (dp->is_edp) - msm_dp_hpd_plug_handle(msm_dp_display); + if (dp->is_edp) { + rc = msm_dp_hpd_plug_handle(msm_dp_display); + if (rc) + return; + } if (pm_runtime_resume_and_get(&dp->pdev->dev)) { DRM_ERROR("failed to pm_runtime_resume\n"); return; } - if (msm_dp_display->link->sink_count == 0) - return; + 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_set_mode(dp, &msm_dp_display->msm_dp_mode); if (rc) { DRM_ERROR("Failed to perform a mode set, rc=%d\n", rc); - return; + goto failed; } - if (!dp->power_on) { - msm_dp_display_host_phy_init(msm_dp_display); + 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); + rc = msm_dp_ctrl_on_link(msm_dp_display->ctrl, false); if (rc) { DRM_ERROR("Failed link training (rc=%d)\n", rc); - // TODO: schedule drm_connector_set_link_status_property() - return; + goto failed; } - msm_dp_display_enable(msm_dp_display, force_link_train); + rc = msm_dp_display_enable(msm_dp_display, force_link_train); + if (rc) + goto failed; rc = msm_dp_display_post_enable(dp); if (rc) { DRM_ERROR("DP display post enable failed, rc=%d\n", rc); - msm_dp_display_disable(msm_dp_display); + goto failed; } + 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_bridge_atomic_disable(struct drm_bridge *drm_bridge, @@ -1428,10 +2119,21 @@ void msm_dp_bridge_atomic_disable(struct drm_bridge *drm_bridge, 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); - msm_dp_ctrl_push_idle(msm_dp_display->ctrl); + ret = pm_runtime_resume_and_get(&dp->pdev->dev); + 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); } void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, @@ -1440,17 +2142,32 @@ void msm_dp_bridge_atomic_post_disable(struct drm_bridge *drm_bridge, 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; msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); - if (dp->is_edp) - msm_dp_hpd_unplug_handle(msm_dp_display); - - msm_dp_display_disable(msm_dp_display); + 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); - pm_runtime_put_sync(&dp->pdev->dev); + 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_bridge_mode_set(struct drm_bridge *drm_bridge, @@ -1464,6 +2181,7 @@ void msm_dp_bridge_mode_set(struct drm_bridge *drm_bridge, msm_dp_display = container_of(dp, struct msm_dp_display_private, msm_dp_display); msm_dp_panel = msm_dp_display->panel; + guard(mutex)(&msm_dp_display->state_lock); memset(&msm_dp_display->msm_dp_mode, 0x0, sizeof(struct msm_dp_display_mode)); @@ -1497,6 +2215,8 @@ 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); + bool put_temporary = true; + unsigned int puts = 0; /* * this is for external DP with hpd irq enabled case, @@ -1511,8 +2231,16 @@ 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) @@ -1520,11 +2248,19 @@ 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); + unsigned int puts = 0; + int ret; - msm_dp_aux_hpd_intr_disable(dp->aux); - msm_dp_aux_hpd_disable(dp->aux); - - pm_runtime_put_sync(&msm_dp_display->pdev->dev); + ret = pm_runtime_resume_and_get(&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, @@ -1536,13 +2272,21 @@ void msm_dp_bridge_hpd_notify(struct drm_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); 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); @@ -1550,16 +2294,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 0b65e16c790da4..979343f038fa4a 100644 --- a/drivers/gpu/drm/msm/dp/dp_display.h +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -29,8 +29,6 @@ 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); diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c index 2ebaa16a1f7ef5..f50f91db00e3d5 100644 --- a/drivers/phy/qualcomm/phy-qcom-qmp-combo.c +++ b/drivers/phy/qualcomm/phy-qcom-qmp-combo.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -2265,6 +2266,33 @@ struct qmp_phy_cfg { bool invert_cc_polarity; }; +enum qmp_combo_reg_ownership { + QMP_COM_REG_NONE, + QMP_COM_REG_HELD, + QMP_COM_REG_UNKNOWN, +}; + +enum qmp_combo_reset_state { + QMP_COM_RESET_UNKNOWN, + QMP_COM_RESET_ASSERTED, + QMP_COM_RESET_DEASSERTED, +}; + +/* Successful consumer API references, not measured electrical state. */ +struct qmp_combo_common_state { + enum qmp_combo_reg_ownership regulators; + enum qmp_combo_reset_state resets; + bool clocks_held; + bool pipe_held; + bool configured; + bool autonomous; + bool runtime_suspended; + int last_error; + /* Bulk rollback errors cannot establish consumer ownership reliably. */ + int quarantine_error; + int cleanup_error; +}; + struct qmp_combo { struct device *dev; @@ -2294,18 +2322,31 @@ struct qmp_combo { struct regulator_bulk_data *vregs; struct mutex phy_mutex; + /* Normal PHY references only; Type-C reconfiguration never changes these. */ int init_count; + struct qmp_combo_common_state common; + /* Requested configuration, separate from the last successful application. */ enum qmpphy_mode qmpphy_mode; + /* Common-register tuple; not yet a successful complete Type-C application. */ + enum qmpphy_mode common_mode; + enum typec_orientation common_orientation; + enum qmpphy_mode applied_mode; + enum typec_orientation applied_orientation; + bool applied_valid; struct phy *usb_phy; enum phy_mode phy_mode; unsigned int usb_init_count; + bool usb_active; struct phy *dp_phy; unsigned int dp_aux_cfg; struct phy_configure_opts_dp dp_opts; unsigned int dp_init_count; + /* Logical generic-PHY power reference, retained across a forced restart. */ bool dp_powered_on; + bool dp_active; + bool dp_aux_active; struct clk_fixed_rate pipe_clk_fixed; struct clk_hw dp_link_hw; @@ -3170,7 +3211,7 @@ static void qmp_v3_configure_dp_tx(struct qmp_combo *qmp) static bool qmp_combo_configure_dp_mode(struct qmp_combo *qmp) { - bool reverse = (qmp->orientation == TYPEC_ORIENTATION_REVERSE); + bool reverse = (qmp->common_orientation == TYPEC_ORIENTATION_REVERSE); const struct phy_configure_opts_dp *dp_opts = &qmp->dp_opts; u32 val; @@ -3490,7 +3531,7 @@ static int qmp_v456_configure_dp_phy(struct qmp_combo *qmp) static int qmp_v4_configure_dp_phy(struct qmp_combo *qmp) { const struct qmp_phy_cfg *cfg = qmp->cfg; - bool reverse = (qmp->orientation == TYPEC_ORIENTATION_REVERSE); + bool reverse = (qmp->common_orientation == TYPEC_ORIENTATION_REVERSE); const struct phy_configure_opts_dp *dp_opts = &qmp->dp_opts; u32 bias0_en, drvr0_en, bias1_en, drvr1_en; u32 status; @@ -3553,7 +3594,7 @@ static int qmp_v4_configure_dp_phy(struct qmp_combo *qmp) static int qmp_v8_configure_dp_phy(struct qmp_combo *qmp) { const struct qmp_phy_cfg *cfg = qmp->cfg; - bool reverse = (qmp->orientation == TYPEC_ORIENTATION_REVERSE); + bool reverse = (qmp->common_orientation == TYPEC_ORIENTATION_REVERSE); const struct phy_configure_opts_dp *dp_opts = &qmp->dp_opts; u32 bias0_en, drvr0_en, bias1_en, drvr1_en; u32 status; @@ -3624,80 +3665,14 @@ static int qmp_v4_calibrate_dp_phy(struct qmp_combo *qmp) return 0; } -static int qmp_combo_dp_configure(struct phy *phy, union phy_configure_opts *opts) -{ - const struct phy_configure_opts_dp *dp_opts = &opts->dp; - struct qmp_combo *qmp = phy_get_drvdata(phy); - const struct qmp_phy_cfg *cfg = qmp->cfg; - - mutex_lock(&qmp->phy_mutex); - - memcpy(&qmp->dp_opts, dp_opts, sizeof(*dp_opts)); - if (qmp->dp_opts.set_voltages) { - cfg->configure_dp_tx(qmp); - qmp->dp_opts.set_voltages = 0; - } - - mutex_unlock(&qmp->phy_mutex); - - return 0; -} - -static int qmp_combo_dp_calibrate(struct phy *phy) -{ - struct qmp_combo *qmp = phy_get_drvdata(phy); - const struct qmp_phy_cfg *cfg = qmp->cfg; - int ret = 0; - - mutex_lock(&qmp->phy_mutex); - - if (cfg->calibrate_dp_phy) - ret = cfg->calibrate_dp_phy(qmp); - - mutex_unlock(&qmp->phy_mutex); - - return ret; -} - -static int qmp_combo_com_init(struct qmp_combo *qmp, bool force) +static void qmp_combo_program_common_locked(struct qmp_combo *qmp) { const struct qmp_phy_cfg *cfg = qmp->cfg; void __iomem *com = qmp->com; void __iomem *pcs_aon = qmp->pcs_aon; - int ret; u32 val; - if (!force && qmp->init_count++) - return 0; - - ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs); - if (ret) { - dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret); - goto err_decrement_count; - } - - ret = reset_control_bulk_assert(cfg->num_resets, qmp->resets); - if (ret) { - dev_err(qmp->dev, "reset assert failed\n"); - goto err_disable_regulators; - } - - ret = reset_control_bulk_deassert(cfg->num_resets, qmp->resets); - if (ret) { - dev_err(qmp->dev, "reset deassert failed\n"); - goto err_disable_regulators; - } - - ret = clk_bulk_prepare_enable(qmp->num_clks, qmp->clks); - if (ret) - goto err_assert_reset; - - /* In DP-only mode, the pipe clk is still required for USB2 */ - ret = clk_prepare_enable(qmp->pipe_clk); - if (ret) { - dev_err(qmp->dev, "pipe_clk enable failed err=%d\n", ret); - goto err_disable_clocks; - } + lockdep_assert_held(&qmp->phy_mutex); qphy_setbits(com, QPHY_V3_DP_COM_POWER_DOWN_CTRL, SW_PWRDN); @@ -3755,83 +3730,329 @@ static int qmp_combo_com_init(struct qmp_combo *qmp, bool force) qphy_setbits(qmp->pcs, cfg->regs[QPHY_PCS_POWER_DOWN_CONTROL], SW_PWRDN); - return 0; + qmp->common_orientation = qmp->orientation; + qmp->common_mode = qmp->qmpphy_mode; +} -err_disable_clocks: - clk_bulk_disable_unprepare(qmp->num_clks, qmp->clks); -err_assert_reset: - reset_control_bulk_assert(cfg->num_resets, qmp->resets); -err_disable_regulators: - regulator_bulk_disable(cfg->num_vregs, qmp->vregs); -err_decrement_count: - qmp->init_count--; +static bool qmp_combo_common_accessible_locked(struct qmp_combo *qmp) +{ + const struct qmp_combo_common_state *state = &qmp->common; - return ret; + lockdep_assert_held(&qmp->phy_mutex); + + return !state->quarantine_error && !state->runtime_suspended && + state->regulators == QMP_COM_REG_HELD && + state->resets == QMP_COM_RESET_DEASSERTED && + state->clocks_held && state->pipe_held; +} + +static bool qmp_combo_common_ready_locked(struct qmp_combo *qmp) +{ + lockdep_assert_held(&qmp->phy_mutex); + + return qmp->common.configured && !qmp->common.autonomous && + qmp_combo_common_accessible_locked(qmp); +} + +static int qmp_combo_common_quarantine_locked(struct qmp_combo *qmp, int error, + bool cleanup) +{ + struct qmp_combo_common_state *state = &qmp->common; + + lockdep_assert_held(&qmp->phy_mutex); + state->configured = false; + qmp->applied_valid = false; + if (!state->quarantine_error) { + state->quarantine_error = error; + dev_err(qmp->dev, "common resource state uncertain after error %d; recovery disabled\n", + error); + } + if (cleanup && !state->cleanup_error) + state->cleanup_error = error; + + return error; +} + +static void qmp_combo_common_put_clocks_locked(struct qmp_combo *qmp) +{ + struct qmp_combo_common_state *state = &qmp->common; + + lockdep_assert_held(&qmp->phy_mutex); + if (state->pipe_held) { + clk_disable_unprepare(qmp->pipe_clk); + state->pipe_held = false; + } + if (state->clocks_held) { + clk_bulk_disable_unprepare(qmp->num_clks, qmp->clks); + state->clocks_held = false; + } } -static int qmp_combo_com_exit(struct qmp_combo *qmp, bool force) +/* Raw release: the caller owns sub-PHY quiesce and logical reference commit. */ +static int qmp_combo_common_release_locked(struct qmp_combo *qmp) { const struct qmp_phy_cfg *cfg = qmp->cfg; + struct qmp_combo_common_state *state = &qmp->common; + int ret; - if (!force && --qmp->init_count) + lockdep_assert_held(&qmp->phy_mutex); + state->configured = false; + qmp->applied_valid = false; + if (state->quarantine_error) + return state->quarantine_error; + if (state->regulators == QMP_COM_REG_UNKNOWN) + return qmp_combo_common_quarantine_locked(qmp, -EIO, true); + if (state->regulators == QMP_COM_REG_NONE) { + if (WARN_ON_ONCE(state->clocks_held || state->pipe_held)) + return qmp_combo_common_quarantine_locked(qmp, -EIO, true); + state->autonomous = false; + state->runtime_suspended = false; return 0; + } - reset_control_bulk_assert(cfg->num_resets, qmp->resets); - - clk_disable_unprepare(qmp->pipe_clk); - clk_bulk_disable_unprepare(qmp->num_clks, qmp->clks); + ret = reset_control_bulk_assert(cfg->num_resets, qmp->resets); + if (ret) { + state->resets = QMP_COM_RESET_UNKNOWN; + state->last_error = ret; + /* Failed quiescence: keep still-owned clocks and supplies. */ + return qmp_combo_common_quarantine_locked(qmp, ret, true); + } + state->resets = QMP_COM_RESET_ASSERTED; + state->autonomous = false; + state->runtime_suspended = false; + qmp_combo_common_put_clocks_locked(qmp); - regulator_bulk_disable(cfg->num_vregs, qmp->vregs); + ret = regulator_bulk_disable(cfg->num_vregs, qmp->vregs); + if (ret) { + /* A bulk error can precede or follow consumer-reference changes. */ + state->regulators = QMP_COM_REG_UNKNOWN; + state->last_error = ret; + return qmp_combo_common_quarantine_locked(qmp, ret, true); + } + state->regulators = QMP_COM_REG_NONE; return 0; } -static int qmp_combo_dp_init(struct phy *phy) +/* Raw acquire never creates or consumes a normal PHY reference. */ +static int qmp_combo_common_acquire_locked(struct qmp_combo *qmp) { - struct qmp_combo *qmp = phy_get_drvdata(phy); const struct qmp_phy_cfg *cfg = qmp->cfg; - int ret; + struct qmp_combo_common_state *state = &qmp->common; + int ret, cleanup; + + lockdep_assert_held(&qmp->phy_mutex); + if (state->quarantine_error) + return state->quarantine_error; + if (state->configured) + return qmp_combo_common_ready_locked(qmp) ? 0 : -EAGAIN; + if (state->regulators != QMP_COM_REG_NONE || state->clocks_held || + state->pipe_held) + return -EBUSY; - mutex_lock(&qmp->phy_mutex); + ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs); + if (ret) { + state->regulators = QMP_COM_REG_UNKNOWN; + state->last_error = ret; + return qmp_combo_common_quarantine_locked(qmp, ret, false); + } + state->regulators = QMP_COM_REG_HELD; + + ret = reset_control_bulk_assert(cfg->num_resets, qmp->resets); + if (ret) { + state->resets = QMP_COM_RESET_UNKNOWN; + state->last_error = ret; + return qmp_combo_common_quarantine_locked(qmp, ret, false); + } + state->resets = QMP_COM_RESET_ASSERTED; - ret = qmp_combo_com_init(qmp, false); + ret = reset_control_bulk_deassert(cfg->num_resets, qmp->resets); + if (ret) { + state->resets = QMP_COM_RESET_UNKNOWN; + state->last_error = ret; + return qmp_combo_common_quarantine_locked(qmp, ret, false); + } + state->resets = QMP_COM_RESET_DEASSERTED; + + ret = clk_bulk_prepare_enable(qmp->num_clks, qmp->clks); if (ret) - goto out_unlock; + goto unwind; + state->clocks_held = true; - cfg->dp_aux_init(qmp); + /* The pipe clock is also required for USB2 in DP-only mode. */ + ret = clk_prepare_enable(qmp->pipe_clk); + if (ret) + goto unwind; + state->pipe_held = true; - qmp->dp_init_count++; + qmp_combo_program_common_locked(qmp); + state->configured = true; + state->autonomous = false; + state->runtime_suspended = false; + state->last_error = 0; -out_unlock: - mutex_unlock(&qmp->phy_mutex); - return ret; + return 0; + +unwind: + /* Preserve the failed-acquire bulk clocks -> reset -> supplies order. */ + qmp_combo_common_put_clocks_locked(qmp); + cleanup = qmp_combo_common_release_locked(qmp); + if (cleanup && !state->cleanup_error) + state->cleanup_error = cleanup; + state->last_error = ret; + + return cleanup ?: ret; } -static int qmp_combo_dp_exit(struct phy *phy) +static int qmp_combo_common_prepare_locked(struct qmp_combo *qmp) { - struct qmp_combo *qmp = phy_get_drvdata(phy); + lockdep_assert_held(&qmp->phy_mutex); + if (WARN_ON_ONCE(qmp->init_count < 0)) + return -EINVAL; + if (qmp->common.quarantine_error) + return qmp->common.quarantine_error; + if (qmp->init_count) { + if (!qmp_combo_common_ready_locked(qmp) || !qmp->applied_valid) + return qmp->common.last_error ?: -EIO; + if (qmp->common_orientation != qmp->orientation || + qmp->common_mode != qmp->qmpphy_mode) + return -EBUSY; + return 0; + } - mutex_lock(&qmp->phy_mutex); + return qmp_combo_common_acquire_locked(qmp); +} + +/* Component initialization can fail after common acquisition: commit last. */ +static int qmp_combo_common_commit_ref_locked(struct qmp_combo *qmp) +{ + lockdep_assert_held(&qmp->phy_mutex); + if (!qmp_combo_common_ready_locked(qmp)) + return qmp->common.quarantine_error ?: (qmp->common.last_error ?: -EIO); + if (WARN_ON_ONCE(qmp->init_count < 0)) + return -EINVAL; + if (WARN_ON_ONCE(qmp->init_count == INT_MAX)) + return -EOVERFLOW; + qmp->init_count++; - qmp_combo_com_exit(qmp, false); + return 0; +} - qmp->dp_init_count--; +static int qmp_combo_common_abort_prepare_locked(struct qmp_combo *qmp, int error) +{ + int cleanup; - mutex_unlock(&qmp->phy_mutex); + lockdep_assert_held(&qmp->phy_mutex); + if (WARN_ON_ONCE(qmp->init_count < 0 || error >= 0)) + return -EINVAL; + qmp->common.last_error = error; + if (!qmp->init_count) { + cleanup = qmp_combo_common_release_locked(qmp); + if (cleanup) { + dev_err(qmp->dev, "PHY initialization failed: %d; cleanup failed: %d\n", + error, cleanup); + return cleanup; + } + } + + return error; +} + +/* Consume a normal reference only after any fallible final release succeeds. */ +static int qmp_combo_common_put_ref_locked(struct qmp_combo *qmp) +{ + int ret; + + lockdep_assert_held(&qmp->phy_mutex); + if (WARN_ON_ONCE(qmp->init_count <= 0)) + return -EINVAL; + if (qmp->init_count > 1) { + qmp->init_count--; + return 0; + } + ret = qmp_combo_common_release_locked(qmp); + if (ret) + return ret; + qmp->init_count--; return 0; } -static int qmp_combo_dp_power_on(struct phy *phy) +static void qmp_combo_commit_applied_locked(struct qmp_combo *qmp) +{ + lockdep_assert_held(&qmp->phy_mutex); + qmp->applied_orientation = qmp->orientation; + qmp->applied_mode = qmp->qmpphy_mode; + qmp->applied_valid = true; +} + +static int qmp_combo_operational_locked(struct qmp_combo *qmp) +{ + lockdep_assert_held(&qmp->phy_mutex); + if (qmp->common.quarantine_error) + return qmp->common.quarantine_error; + if (!qmp->applied_valid || !qmp_combo_common_ready_locked(qmp)) + return qmp->common.last_error ?: -EIO; + + return 0; +} + +static int qmp_combo_dp_aux_init_locked(struct qmp_combo *qmp) +{ + const struct qmp_phy_cfg *cfg = qmp->cfg; + + lockdep_assert_held(&qmp->phy_mutex); + if (!qmp_combo_common_ready_locked(qmp)) + return qmp->common.quarantine_error ?: -EIO; + if (qmp->common_mode == QMPPHY_MODE_USB3_ONLY) + return -EBUSY; + + cfg->dp_aux_init(qmp); + qmp->dp_aux_active = true; + + return 0; +} + +static int qmp_combo_dp_power_off_locked(struct qmp_combo *qmp) +{ + lockdep_assert_held(&qmp->phy_mutex); + if (qmp->common.quarantine_error) + return qmp->common.quarantine_error; + if (!qmp->dp_active && !qmp->dp_aux_active) + return 0; + if (!qmp_combo_common_accessible_locked(qmp)) + return -EIO; + + /* Assert DP PHY power down, including an AUX-only initialization. */ + writel(DP_PHY_PD_CTL_PSR_PWRDN, qmp->dp_dp_phy + QSERDES_DP_PHY_PD_CTL); + qmp->dp_active = false; + qmp->dp_aux_active = false; + + return 0; +} + +static int qmp_combo_dp_power_on_locked(struct qmp_combo *qmp) { - struct qmp_combo *qmp = phy_get_drvdata(phy); const struct qmp_phy_cfg *cfg = qmp->cfg; void __iomem *tx = qmp->dp_tx; void __iomem *tx2 = qmp->dp_tx2; + int ret, cleanup; - mutex_lock(&qmp->phy_mutex); + lockdep_assert_held(&qmp->phy_mutex); + if (!qmp_combo_common_ready_locked(qmp)) + return qmp->common.quarantine_error ?: -EIO; + if (!qmp->dp_init_count) + return -EINVAL; + if (qmp->common_mode == QMPPHY_MODE_USB3_ONLY) + return -EBUSY; + if (qmp->dp_active) + return 0; - qmp_combo_dp_serdes_init(qmp); + /* Record cleanup demand before the first potentially partial programming. */ + qmp->dp_aux_active = true; + ret = qmp_combo_dp_serdes_init(qmp); + if (ret) + goto err_power_down; qmp_configure_lane(qmp->dev, tx, cfg->dp_tx_tbl, cfg->dp_tx_tbl_num, 1); qmp_configure_lane(qmp->dev, tx2, cfg->dp_tx_tbl, cfg->dp_tx_tbl_num, 2); @@ -3840,34 +4061,200 @@ static int qmp_combo_dp_power_on(struct phy *phy) cfg->configure_dp_tx(qmp); /* Configure link rate, swing, etc. */ - cfg->configure_dp_phy(qmp); + ret = cfg->configure_dp_phy(qmp); + if (ret) + goto err_power_down; + + qmp->dp_active = true; + + return 0; - qmp->dp_powered_on = true; +err_power_down: + cleanup = qmp_combo_dp_power_off_locked(qmp); + return cleanup ?: ret; +} +static int qmp_combo_dp_init(struct phy *phy) +{ + struct qmp_combo *qmp = phy_get_drvdata(phy); + int ret, cleanup; + + mutex_lock(&qmp->phy_mutex); + if (WARN_ON_ONCE(qmp->dp_init_count)) { + ret = -EINVAL; + goto out_unlock; + } + if (qmp->common.quarantine_error) { + ret = qmp->common.quarantine_error; + goto out_unlock; + } + /* A new DP initialization must prepare AUX, unlike a retained mux owner. */ + if (qmp->qmpphy_mode == QMPPHY_MODE_USB3_ONLY) { + ret = -EBUSY; + goto out_unlock; + } + ret = qmp_combo_common_prepare_locked(qmp); + if (ret) + goto out_unlock; + ret = qmp_combo_dp_aux_init_locked(qmp); + if (ret) + goto err_abort; + ret = qmp_combo_common_commit_ref_locked(qmp); + if (ret) + goto err_power_down; + qmp->dp_init_count++; + qmp_combo_commit_applied_locked(qmp); + goto out_unlock; + +err_power_down: + cleanup = qmp_combo_dp_power_off_locked(qmp); + if (cleanup) + ret = cleanup; +err_abort: + ret = qmp_combo_common_abort_prepare_locked(qmp, ret); +out_unlock: mutex_unlock(&qmp->phy_mutex); + return ret; +} - return 0; +static int qmp_combo_dp_exit(struct phy *phy) +{ + struct qmp_combo *qmp = phy_get_drvdata(phy); + int ret; + + mutex_lock(&qmp->phy_mutex); + if (WARN_ON_ONCE(!qmp->dp_init_count)) { + ret = -EINVAL; + goto out_unlock; + } + if (qmp->dp_powered_on) { + ret = -EBUSY; + goto out_unlock; + } + ret = qmp_combo_dp_power_off_locked(qmp); + if (ret) + goto out_unlock; + ret = qmp_combo_common_put_ref_locked(qmp); + if (!ret) + qmp->dp_init_count--; + +out_unlock: + mutex_unlock(&qmp->phy_mutex); + return ret; } -static int qmp_combo_dp_power_off(struct phy *phy) +static int qmp_combo_dp_power_on(struct phy *phy) { struct qmp_combo *qmp = phy_get_drvdata(phy); + int ret; mutex_lock(&qmp->phy_mutex); + ret = qmp_combo_operational_locked(qmp); + if (ret) + goto out_unlock; + if (!qmp->dp_init_count || qmp->dp_powered_on) { + ret = -EINVAL; + goto out_unlock; + } + ret = qmp_combo_dp_power_on_locked(qmp); + if (!ret) + qmp->dp_powered_on = true; - /* Assert DP PHY power down */ - writel(DP_PHY_PD_CTL_PSR_PWRDN, qmp->dp_dp_phy + QSERDES_DP_PHY_PD_CTL); +out_unlock: + mutex_unlock(&qmp->phy_mutex); + return ret; +} - qmp->dp_powered_on = false; +static int qmp_combo_dp_power_off(struct phy *phy) +{ + struct qmp_combo *qmp = phy_get_drvdata(phy); + int ret; + + mutex_lock(&qmp->phy_mutex); + if (!qmp->dp_powered_on) { + ret = -EINVAL; + goto out_unlock; + } + ret = qmp_combo_dp_power_off_locked(qmp); + if (!ret) + qmp->dp_powered_on = false; +out_unlock: mutex_unlock(&qmp->phy_mutex); + return ret; +} - return 0; +static int qmp_combo_dp_configure(struct phy *phy, union phy_configure_opts *opts) +{ + struct qmp_combo *qmp = phy_get_drvdata(phy); + const struct qmp_phy_cfg *cfg = qmp->cfg; + bool voltages = opts->dp.set_voltages; + int ret; + + /* Generic phy_configure() does not take a runtime-PM reference. */ + if (voltages) { + ret = pm_runtime_resume_and_get(qmp->dev); + if (ret < 0) + return ret; + } + mutex_lock(&qmp->phy_mutex); + ret = qmp->common.quarantine_error; + if (ret) + goto out_unlock; + if (voltages) { + ret = qmp_combo_operational_locked(qmp); + if (ret) + goto out_unlock; + if (!qmp->dp_init_count || (!qmp->dp_active && !qmp->dp_aux_active) || + qmp->common_mode == QMPPHY_MODE_USB3_ONLY) { + ret = -EIO; + goto out_unlock; + } + } + memcpy(&qmp->dp_opts, &opts->dp, sizeof(qmp->dp_opts)); + if (voltages) { + cfg->configure_dp_tx(qmp); + qmp->dp_opts.set_voltages = 0; + } + +out_unlock: + mutex_unlock(&qmp->phy_mutex); + if (voltages) + pm_runtime_put(qmp->dev); + return ret; } -static int qmp_combo_usb_power_on(struct phy *phy) +static int qmp_combo_dp_calibrate(struct phy *phy) { struct qmp_combo *qmp = phy_get_drvdata(phy); + const struct qmp_phy_cfg *cfg = qmp->cfg; + int ret; + + ret = pm_runtime_resume_and_get(qmp->dev); + if (ret < 0) + return ret; + mutex_lock(&qmp->phy_mutex); + ret = qmp_combo_operational_locked(qmp); + if (ret) + goto out_unlock; + if (!qmp->dp_init_count || (!qmp->dp_active && !qmp->dp_aux_active) || + qmp->common_mode == QMPPHY_MODE_USB3_ONLY) { + ret = -EIO; + goto out_unlock; + } + if (cfg->calibrate_dp_phy) + ret = cfg->calibrate_dp_phy(qmp); + +out_unlock: + mutex_unlock(&qmp->phy_mutex); + pm_runtime_put(qmp->dev); + return ret; +} + +static int qmp_combo_usb_power_off_locked(struct qmp_combo *qmp); + +static int qmp_combo_usb_power_on_locked(struct qmp_combo *qmp) +{ const struct qmp_phy_cfg *cfg = qmp->cfg; void __iomem *serdes = qmp->serdes; void __iomem *tx = qmp->tx; @@ -3878,8 +4265,18 @@ static int qmp_combo_usb_power_on(struct phy *phy) void __iomem *pcs_usb = qmp->pcs_usb; void __iomem *status; unsigned int val; - int ret; + int ret, cleanup; + + lockdep_assert_held(&qmp->phy_mutex); + if (!qmp_combo_common_ready_locked(qmp)) + return qmp->common.quarantine_error ?: -EIO; + if (qmp->common_mode == QMPPHY_MODE_DP_ONLY) + return -EBUSY; + if (qmp->usb_active) + return 0; + /* A timeout must quiesce the partial PHY, not release common's clock. */ + qmp->usb_active = true; qmp_configure(qmp->dev, serdes, cfg->serdes_tbl, cfg->serdes_tbl_num); /* Tx, Rx, and PCS configurations */ @@ -3911,22 +4308,26 @@ static int qmp_combo_usb_power_on(struct phy *phy) PHY_INIT_COMPLETE_TIMEOUT); if (ret) { dev_err(qmp->dev, "phy initialization timed-out\n"); - goto err_disable_pipe_clk; + cleanup = qmp_combo_usb_power_off_locked(qmp); + if (cleanup) + return cleanup; } - return 0; - -err_disable_pipe_clk: - clk_disable_unprepare(qmp->pipe_clk); - return ret; } -static int qmp_combo_usb_power_off(struct phy *phy) +static int qmp_combo_usb_power_off_locked(struct qmp_combo *qmp) { - struct qmp_combo *qmp = phy_get_drvdata(phy); const struct qmp_phy_cfg *cfg = qmp->cfg; + lockdep_assert_held(&qmp->phy_mutex); + if (qmp->common.quarantine_error) + return qmp->common.quarantine_error; + if (!qmp->usb_active) + return 0; + if (!qmp_combo_common_accessible_locked(qmp)) + return -EIO; + /* PHY reset */ qphy_setbits(qmp->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET); @@ -3937,6 +4338,7 @@ static int qmp_combo_usb_power_off(struct phy *phy) /* Put PHY into POWER DOWN state: active low */ qphy_clrbits(qmp->pcs, cfg->regs[QPHY_PCS_POWER_DOWN_CONTROL], SW_PWRDN); + qmp->usb_active = false; return 0; } @@ -3944,21 +4346,35 @@ static int qmp_combo_usb_power_off(struct phy *phy) static int qmp_combo_usb_init(struct phy *phy) { struct qmp_combo *qmp = phy_get_drvdata(phy); - int ret; + int ret, cleanup; mutex_lock(&qmp->phy_mutex); - ret = qmp_combo_com_init(qmp, false); + if (WARN_ON_ONCE(qmp->usb_init_count)) { + ret = -EINVAL; + goto out_unlock; + } + ret = qmp_combo_common_prepare_locked(qmp); if (ret) goto out_unlock; - ret = qmp_combo_usb_power_on(phy); - if (ret) { - qmp_combo_com_exit(qmp, false); - goto out_unlock; + if (qmp->common_mode != QMPPHY_MODE_DP_ONLY) { + ret = qmp_combo_usb_power_on_locked(qmp); + if (ret) + goto err_abort; } - + ret = qmp_combo_common_commit_ref_locked(qmp); + if (ret) + goto err_power_down; qmp->usb_init_count++; - + qmp_combo_commit_applied_locked(qmp); + goto out_unlock; + +err_power_down: + cleanup = qmp_combo_usb_power_off_locked(qmp); + if (cleanup) + ret = cleanup; +err_abort: + ret = qmp_combo_common_abort_prepare_locked(qmp, ret); out_unlock: mutex_unlock(&qmp->phy_mutex); return ret; @@ -3970,11 +4386,15 @@ static int qmp_combo_usb_exit(struct phy *phy) int ret; mutex_lock(&qmp->phy_mutex); - ret = qmp_combo_usb_power_off(phy); + if (WARN_ON_ONCE(!qmp->usb_init_count)) { + ret = -EINVAL; + goto out_unlock; + } + ret = qmp_combo_usb_power_off_locked(qmp); if (ret) goto out_unlock; - ret = qmp_combo_com_exit(qmp, false); + ret = qmp_combo_common_put_ref_locked(qmp); if (ret) goto out_unlock; @@ -3989,14 +4409,33 @@ static int qmp_combo_usb_set_mode(struct phy *phy, enum phy_mode mode, int submo { struct qmp_combo *qmp = phy_get_drvdata(phy); + mutex_lock(&qmp->phy_mutex); qmp->phy_mode = mode; + mutex_unlock(&qmp->phy_mutex); return 0; } +static int qmp_combo_usb_power_on(struct phy *phy) +{ + struct qmp_combo *qmp = phy_get_drvdata(phy); + int ret; + + /* Generic PHY takes PM before invoking this zero-to-one power check. */ + mutex_lock(&qmp->phy_mutex); + ret = qmp_combo_operational_locked(qmp); + if (!ret && (!qmp->usb_init_count || + (qmp->common_mode != QMPPHY_MODE_DP_ONLY && !qmp->usb_active))) + ret = -EIO; + mutex_unlock(&qmp->phy_mutex); + + return ret; +} + static const struct phy_ops qmp_combo_usb_phy_ops = { .init = qmp_combo_usb_init, .exit = qmp_combo_usb_exit, + .power_on = qmp_combo_usb_power_on, .set_mode = qmp_combo_usb_set_mode, .owner = THIS_MODULE, }; @@ -4070,48 +4509,75 @@ static void qmp_combo_disable_autonomous_mode(struct qmp_combo *qmp) static int __maybe_unused qmp_combo_runtime_suspend(struct device *dev) { struct qmp_combo *qmp = dev_get_drvdata(dev); + struct qmp_combo_common_state *state = &qmp->common; + int ret = 0; - dev_vdbg(dev, "Suspending QMP phy, mode:%d\n", qmp->phy_mode); - - if (!qmp->init_count) { - dev_vdbg(dev, "PHY not initialized, bailing out\n"); - return 0; + mutex_lock(&qmp->phy_mutex); + /* Callback success permits genpd to remove power: unknown is not off. */ + if (state->quarantine_error) { + ret = -EBUSY; + goto out_unlock; } + if (!state->configured || !qmp->applied_valid) { + if (state->regulators != QMP_COM_REG_NONE || state->clocks_held || + state->pipe_held) + ret = -EBUSY; + goto out_unlock; + } + if (state->runtime_suspended) + goto out_unlock; + if (!qmp_combo_common_ready_locked(qmp)) { + ret = -EIO; + goto out_unlock; + } + if (qmp->usb_active) { + qmp_combo_enable_autonomous_mode(qmp); + state->autonomous = true; + } + qmp_combo_common_put_clocks_locked(qmp); + state->runtime_suspended = true; - qmp_combo_enable_autonomous_mode(qmp); - - clk_disable_unprepare(qmp->pipe_clk); - clk_bulk_disable_unprepare(qmp->num_clks, qmp->clks); - - return 0; +out_unlock: + mutex_unlock(&qmp->phy_mutex); + return ret; } static int __maybe_unused qmp_combo_runtime_resume(struct device *dev) { struct qmp_combo *qmp = dev_get_drvdata(dev); + struct qmp_combo_common_state *state = &qmp->common; int ret = 0; - dev_vdbg(dev, "Resuming QMP phy, mode:%d\n", qmp->phy_mode); - - if (!qmp->init_count) { - dev_vdbg(dev, "PHY not initialized, bailing out\n"); - return 0; - } + mutex_lock(&qmp->phy_mutex); + /* Permit logical cleanup to reach the provider, without certifying readiness. */ + if (state->quarantine_error || !state->configured || !state->runtime_suspended) + goto out_unlock; ret = clk_bulk_prepare_enable(qmp->num_clks, qmp->clks); if (ret) - return ret; + goto out_error; + state->clocks_held = true; ret = clk_prepare_enable(qmp->pipe_clk); if (ret) { - dev_err(dev, "pipe_clk enable failed, err=%d\n", ret); - clk_bulk_disable_unprepare(qmp->num_clks, qmp->clks); - return ret; + qmp_combo_common_put_clocks_locked(qmp); + goto out_error; } + state->pipe_held = true; + state->runtime_suspended = false; - qmp_combo_disable_autonomous_mode(qmp); + if (state->autonomous) { + qmp_combo_disable_autonomous_mode(qmp); + state->autonomous = false; + } + state->last_error = 0; + goto out_unlock; - return 0; +out_error: + state->last_error = ret; +out_unlock: + mutex_unlock(&qmp->phy_mutex); + return ret; } static const struct dev_pm_ops qmp_combo_pm_ops = { @@ -4424,63 +4890,199 @@ static int qmp_combo_register_clocks(struct qmp_combo *qmp, struct device_node * } #if IS_ENABLED(CONFIG_TYPEC) -static int qmp_combo_typec_switch_set(struct typec_switch_dev *sw, - enum typec_orientation orientation) +enum qmp_combo_typec_request_kind { + QMP_COMBO_TYPEC_ORIENTATION, + QMP_COMBO_TYPEC_MODE, +}; + +static bool qmp_combo_typec_applied_locked(struct qmp_combo *qmp, + enum typec_orientation orientation, + enum qmpphy_mode mode) { - struct qmp_combo *qmp = typec_switch_get_drvdata(sw); - const struct qmp_phy_cfg *cfg = qmp->cfg; + lockdep_assert_held(&qmp->phy_mutex); + + return qmp->applied_valid && qmp_combo_common_ready_locked(qmp) && + qmp->applied_orientation == orientation && qmp->applied_mode == mode; +} + +static int qmp_combo_typec_abort_locked(struct qmp_combo *qmp, int cause) +{ + int ret; + + lockdep_assert_held(&qmp->phy_mutex); + qmp->applied_valid = false; + qmp->common.last_error = cause; + if (qmp->common.quarantine_error) + return qmp->common.quarantine_error; + + ret = qmp_combo_dp_power_off_locked(qmp); + if (ret) + goto cleanup_failed; + ret = qmp_combo_usb_power_off_locked(qmp); + if (ret) + goto cleanup_failed; + ret = qmp_combo_common_release_locked(qmp); + if (ret) + goto cleanup_failed; + + return cause; - /* print out call parameter */ - printk(KERN_INFO "%s: qmp_combo_switch_set() enter new_orientation=%d, altmode=%d, orientation=%d\n", dev_name(&sw->dev),orientation,qmp->dp_powered_on,qmp->orientation); +cleanup_failed: + dev_err(qmp->dev, "Type-C reconfiguration failed: %d; cleanup failed: %d\n", + cause, ret); + qmp->common.last_error = cause; + return qmp->common.quarantine_error ?: ret; +} - if (orientation == qmp->orientation || orientation == TYPEC_ORIENTATION_NONE) +static int qmp_combo_typec_reconfigure_locked(struct qmp_combo *qmp) +{ + int ret; + + lockdep_assert_held(&qmp->phy_mutex); + if (qmp->common.quarantine_error) + return qmp->common.quarantine_error; + if (!qmp->init_count) { + qmp->applied_valid = false; return 0; + } + /* Do not change mode underneath a retained logical DP power demand. */ + if (qmp->dp_powered_on && qmp->qmpphy_mode != qmp->applied_mode) + return -EBUSY; + if (qmp_combo_typec_applied_locked(qmp, qmp->orientation, qmp->qmpphy_mode)) + return 0; + + qmp->applied_valid = false; + ret = qmp_combo_usb_power_off_locked(qmp); + if (ret) + return qmp_combo_typec_abort_locked(qmp, ret); + ret = qmp_combo_dp_power_off_locked(qmp); + if (ret) + return qmp_combo_typec_abort_locked(qmp, ret); + ret = qmp_combo_common_release_locked(qmp); + if (ret) + return ret; + ret = qmp_combo_common_acquire_locked(qmp); + if (ret) + return qmp_combo_typec_abort_locked(qmp, ret); + + if (qmp->usb_init_count && qmp->common_mode != QMPPHY_MODE_DP_ONLY) { + ret = qmp_combo_usb_power_on_locked(qmp); + if (ret) + return qmp_combo_typec_abort_locked(qmp, ret); + } + if (qmp->dp_init_count && qmp->common_mode != QMPPHY_MODE_USB3_ONLY) { + ret = qmp_combo_dp_aux_init_locked(qmp); + if (ret) + return qmp_combo_typec_abort_locked(qmp, ret); + } + if (qmp->dp_powered_on) { + ret = qmp_combo_dp_power_on_locked(qmp); + if (ret) + return qmp_combo_typec_abort_locked(qmp, ret); + } + qmp_combo_commit_applied_locked(qmp); + + return 0; +} +static void qmp_combo_typec_cache_request_locked(struct qmp_combo *qmp, + enum qmp_combo_typec_request_kind kind, + enum typec_orientation orientation, + enum qmpphy_mode mode) +{ + lockdep_assert_held(&qmp->phy_mutex); + if (kind == QMP_COMBO_TYPEC_ORIENTATION) + qmp->orientation = orientation; + else + qmp->qmpphy_mode = mode; +} + +static int qmp_combo_typec_request(struct qmp_combo *qmp, + enum qmp_combo_typec_request_kind kind, + enum typec_orientation orientation, + enum qmpphy_mode mode) +{ + enum typec_orientation desired_orientation; + enum qmpphy_mode desired_mode; + bool pm_held = false; + int ret; + +again: mutex_lock(&qmp->phy_mutex); - qmp->orientation = orientation; + if (qmp->common.quarantine_error) { + ret = qmp->common.quarantine_error; + goto out_unlock; + } + /* Keep disconnect's existing lane-programming no-op, not a power-off claim. */ + if (kind == QMP_COMBO_TYPEC_ORIENTATION && orientation == TYPEC_ORIENTATION_NONE) { + ret = 0; + goto out_unlock; + } + desired_orientation = kind == QMP_COMBO_TYPEC_ORIENTATION ? + orientation : qmp->orientation; + desired_mode = kind == QMP_COMBO_TYPEC_MODE ? mode : qmp->qmpphy_mode; - if (qmp->init_count) { - if (qmp->usb_init_count) - qmp_combo_usb_power_off(qmp->usb_phy); - qmp_combo_com_exit(qmp, true); - - qmp_combo_com_init(qmp, true); - if (qmp->usb_init_count) - qmp_combo_usb_power_on(qmp->usb_phy); - if (qmp->dp_init_count) - cfg->dp_aux_init(qmp); + if (!qmp->init_count) { + qmp_combo_typec_cache_request_locked(qmp, kind, orientation, mode); + qmp->applied_valid = false; + ret = 0; + goto out_unlock; + } + if (qmp->dp_powered_on && desired_mode != qmp->applied_mode) { + /* Pending intent only. Neither successful application nor auto replay. */ + qmp_combo_typec_cache_request_locked(qmp, kind, orientation, mode); + ret = -EBUSY; + goto out_unlock; + } + if (qmp_combo_typec_applied_locked(qmp, desired_orientation, desired_mode)) { + qmp_combo_typec_cache_request_locked(qmp, kind, orientation, mode); + ret = 0; + goto out_unlock; + } + if (!pm_held) { + /* Runtime resume takes phy_mutex: acquire PM before re-locking it. */ + mutex_unlock(&qmp->phy_mutex); + ret = pm_runtime_resume_and_get(qmp->dev); + if (ret < 0) + return ret; + pm_held = true; + /* Re-evaluate before staging: never apply another caller's stale tuple. */ + goto again; } + qmp_combo_typec_cache_request_locked(qmp, kind, orientation, mode); + ret = qmp_combo_typec_reconfigure_locked(qmp); + +out_unlock: mutex_unlock(&qmp->phy_mutex); + if (pm_held) + pm_runtime_put(qmp->dev); + return ret; +} + +static int qmp_combo_typec_switch_set(struct typec_switch_dev *sw, + enum typec_orientation orientation) +{ + struct qmp_combo *qmp = typec_switch_get_drvdata(sw); - /* print out result */ - printk(KERN_INFO "%s: qmp_combo_switch_set() exit new_orientation=%d, altmode=%d, orientation=%d\n", dev_name(&sw->dev),orientation,qmp->dp_powered_on,qmp->orientation); + if (orientation != TYPEC_ORIENTATION_NONE && + orientation != TYPEC_ORIENTATION_NORMAL && orientation != TYPEC_ORIENTATION_REVERSE) + return -EINVAL; - return 0; + return qmp_combo_typec_request(qmp, QMP_COMBO_TYPEC_ORIENTATION, + orientation, QMPPHY_MODE_USB3DP); } static int qmp_combo_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) { struct qmp_combo *qmp = typec_mux_get_drvdata(mux); - const struct qmp_phy_cfg *cfg = qmp->cfg; - enum qmpphy_mode new_mode; - unsigned int svid; + enum qmpphy_mode mode = QMPPHY_MODE_USB3_ONLY; - /* print out call parameter */ - printk(KERN_INFO "%s: qmp_combo_mux_set() enter mode=%d, altmode=%d\n", dev_name(&mux->dev),(int)state->mode,qmp->dp_powered_on); - - guard(mutex)(&qmp->phy_mutex); - - if (state->alt) - svid = state->alt->svid; - else - svid = 0; - - if (svid == USB_TYPEC_DP_SID) { + if (state->alt && state->alt->svid == USB_TYPEC_DP_SID) { switch (state->mode) { /* DP Only */ case TYPEC_DP_STATE_C: case TYPEC_DP_STATE_E: - new_mode = QMPPHY_MODE_DP_ONLY; + mode = QMPPHY_MODE_DP_ONLY; break; /* DP + USB */ @@ -4489,62 +5091,13 @@ static int qmp_combo_typec_mux_set(struct typec_mux_dev *mux, struct typec_mux_s /* Safe fallback...*/ default: - new_mode = QMPPHY_MODE_USB3DP; + mode = QMPPHY_MODE_USB3DP; break; } - } else { - /* No DP SVID => don't care, assume it's just USB3 */ - new_mode = QMPPHY_MODE_USB3_ONLY; - } - - if (new_mode == qmp->qmpphy_mode) { - dev_dbg(qmp->dev, "typec_mux_set: same qmpphy mode, bail out\n"); - return 0; - } - - if (qmp->qmpphy_mode != QMPPHY_MODE_USB3_ONLY && qmp->dp_powered_on) { - dev_dbg(qmp->dev, "typec_mux_set: DP PHY is still in use, delaying switch\n"); - return 0; } - dev_dbg(qmp->dev, "typec_mux_set: switching from qmpphy mode %d to %d\n", - qmp->qmpphy_mode, new_mode); - - qmp->qmpphy_mode = new_mode; - - if (qmp->init_count) { - if (qmp->usb_init_count) - qmp_combo_usb_power_off(qmp->usb_phy); - - if (qmp->dp_init_count) - writel(DP_PHY_PD_CTL_PSR_PWRDN, qmp->dp_dp_phy + QSERDES_DP_PHY_PD_CTL); - - qmp_combo_com_exit(qmp, true); - - /* Now everything's powered down, power up the right PHYs */ - qmp_combo_com_init(qmp, true); - - if (new_mode == QMPPHY_MODE_DP_ONLY) { - if (qmp->usb_init_count) - qmp->usb_init_count--; - } - - if (new_mode == QMPPHY_MODE_USB3DP || new_mode == QMPPHY_MODE_USB3_ONLY) { - qmp_combo_usb_power_on(qmp->usb_phy); - if (!qmp->usb_init_count) - qmp->usb_init_count++; - } - - if (new_mode == QMPPHY_MODE_DP_ONLY || new_mode == QMPPHY_MODE_USB3DP) { - if (qmp->dp_init_count) - cfg->dp_aux_init(qmp); - } - } - - /* print out result */ - printk(KERN_INFO "%s: qmp_combo_mux_set() exit mode=%d, new_mode=%d, altmode=%d\n", dev_name(&mux->dev),(int)state->mode,(int)new_mode,qmp->dp_powered_on); - - return 0; + return qmp_combo_typec_request(qmp, QMP_COMBO_TYPEC_MODE, + TYPEC_ORIENTATION_NONE, mode); } static void qmp_combo_typec_switch_unregister(void *data) diff --git a/drivers/soc/qcom/pmic_glink.c b/drivers/soc/qcom/pmic_glink.c index dd31ae609f74fa..4ca80804af7aae 100644 --- a/drivers/soc/qcom/pmic_glink.c +++ b/drivers/soc/qcom/pmic_glink.c @@ -3,6 +3,7 @@ * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. * Copyright (c) 2022, Linaro Ltd */ +#include #include #include #include @@ -46,6 +47,8 @@ struct pmic_glink { unsigned int client_state; unsigned int pdr_state; bool pdr_available; + /* Updated under state_lock/client_lock; snapshots may be read in callbacks. */ + atomic64_t state_generation; /* serializing clients list updates */ spinlock_t client_lock; @@ -118,14 +121,29 @@ void pmic_glink_client_register(struct pmic_glink_client *client) } EXPORT_SYMBOL_GPL(pmic_glink_client_register); -int pmic_glink_send(struct pmic_glink_client *client, void *data, size_t len) +/** + * pmic_glink_get_generation() - snapshot the current client-state generation + * @client: registered PMIC GLINK client + * + * May be called from a receive or state callback. The token does not imply + * service availability or correlate replies with requests. Pass it to + * pmic_glink_send_generation() to reject a send across a state transition. + * + * Return: An opaque token for this PMIC GLINK instance's client-state generation. + */ +u64 pmic_glink_get_generation(struct pmic_glink_client *client) +{ + return atomic64_read(&client->pg->state_generation); +} +EXPORT_SYMBOL_GPL(pmic_glink_get_generation); + +static int __pmic_glink_send(struct pmic_glink *pg, void *data, size_t len) { - struct pmic_glink *pg = client->pg; bool timeout_reached = false; unsigned long start; int ret; - guard(mutex)(&pg->state_lock); + lockdep_assert_held(&pg->state_lock); if (!pg->ept) { return -ECONNRESET; } @@ -147,8 +165,46 @@ int pmic_glink_send(struct pmic_glink_client *client, void *data, size_t len) return ret; } + +int pmic_glink_send(struct pmic_glink_client *client, void *data, size_t len) +{ + struct pmic_glink *pg = client->pg; + + guard(mutex)(&pg->state_lock); + return __pmic_glink_send(pg, data, len); +} EXPORT_SYMBOL_GPL(pmic_glink_send); +/** + * pmic_glink_send_generation() - send only within a captured service generation + * @client: registered PMIC GLINK client + * @data: message to send + * @len: message length + * @generation: token captured with pmic_glink_get_generation() + * + * Unlike pmic_glink_send(), this requires the client state to be UP as well as + * an endpoint to exist. Check the token under the state lock and keep that lock + * through the send, including backpressure retries. A later reply still needs + * the client's own validation; this does not identify requests or flush replies. + * Must be called from sleepable context, not from a receive or state callback. + * + * Return: 0 on success, -ECONNRESET if the generation changed or the service is + * unavailable, otherwise an error returned by the transport. + */ +int pmic_glink_send_generation(struct pmic_glink_client *client, void *data, + size_t len, u64 generation) +{ + struct pmic_glink *pg = client->pg; + + guard(mutex)(&pg->state_lock); + if (generation != atomic64_read(&pg->state_generation) || + pg->client_state != SERVREG_SERVICE_STATE_UP) + return -ECONNRESET; + + return __pmic_glink_send(pg, data, len); +} +EXPORT_SYMBOL_GPL(pmic_glink_send_generation); + static int pmic_glink_rpmsg_callback(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 addr) { @@ -216,6 +272,8 @@ static void pmic_glink_state_notify_clients(struct pmic_glink *pg) unsigned int new_state = pg->client_state; unsigned long flags; + lockdep_assert_held(&pg->state_lock); + if (pg->client_state != SERVREG_SERVICE_STATE_UP) { if (pg->pdr_state == SERVREG_SERVICE_STATE_UP && pg->ept) new_state = SERVREG_SERVICE_STATE_UP; @@ -226,6 +284,7 @@ static void pmic_glink_state_notify_clients(struct pmic_glink *pg) if (new_state != pg->client_state) { spin_lock_irqsave(&pg->client_lock, flags); + atomic64_inc(&pg->state_generation); list_for_each_entry(client, &pg->clients, node) client->pdr_notify(client->priv, new_state); spin_unlock_irqrestore(&pg->client_lock, flags); @@ -313,6 +372,7 @@ static int pmic_glink_probe(struct platform_device *pdev) INIT_LIST_HEAD(&pg->clients); spin_lock_init(&pg->client_lock); mutex_init(&pg->state_lock); + atomic64_set(&pg->state_generation, 0); pg->data = of_device_get_match_data(&pdev->dev); if (!pg->data) diff --git a/drivers/soc/qcom/pmic_glink_altmode.c b/drivers/soc/qcom/pmic_glink_altmode.c index d579ccb30dde85..8e201aab39c767 100644 --- a/drivers/soc/qcom/pmic_glink_altmode.c +++ b/drivers/soc/qcom/pmic_glink_altmode.c @@ -6,11 +6,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -24,6 +26,8 @@ #include #define PMIC_GLINK_MAX_PORTS 3 +/* Bound firmware-driven memory use; a full queue is reported, not overwritten. */ +#define PMIC_GLINK_MAX_EVENTS 64 #define USBC_SC8180X_NOTIFY_IND 0x13 #define USBC_CMD_WRITE_REQ 0x15 @@ -94,6 +98,19 @@ enum pmic_glink_altmode_pin_assignment { struct pmic_glink_altmode; +struct pmic_glink_altmode_event { + u64 generation; + enum typec_orientation orientation; + u16 svid; + struct usbc_sc8280x_tbt_data tbt_data; + u8 mode; + u8 hpd_state; + u8 hpd_irq; + u8 mux_ctrl; + /* SC8180 notifications do not carry the SC8280 mux-control field. */ + bool has_mux_ctrl; +}; + #define work_to_altmode_port(w) container_of((w), struct pmic_glink_altmode_port, work) struct pmic_glink_altmode_port { @@ -102,24 +119,16 @@ struct pmic_glink_altmode_port { struct typec_switch *typec_switch; struct typec_mux *typec_mux; - struct typec_mux_state state; struct typec_retimer *typec_retimer; - struct typec_retimer_state retimer_state; struct typec_altmode dp_alt; struct typec_altmode tbt_alt; struct work_struct work; + DECLARE_KFIFO(events, struct pmic_glink_altmode_event, PMIC_GLINK_MAX_EVENTS); struct auxiliary_device *bridge; - - enum typec_orientation orientation; - u16 svid; - struct usbc_sc8280x_tbt_data tbt_data; - u8 dp_data; - u8 mode; - u8 hpd_state; - u8 hpd_irq; - u8 mux_ctrl; + /* Last published HPD status, serialized by lifecycle_lock, not readiness. */ + bool hpd_connected; }; #define work_to_altmode(w) container_of((w), struct pmic_glink_altmode, enable_work) @@ -131,18 +140,42 @@ struct pmic_glink_altmode { /* To synchronize WRITE_REQ acks */ struct mutex lock; + /* Serializes provider/HPD apply and reconciliation, never request waits. */ + struct mutex lifecycle_lock; + /* One waiter at a time under lock; completion does not identify a request. */ struct completion pan_ack; struct pmic_glink_client *client; struct work_struct enable_work; + /* Protects queues/state; never held over providers, mutexes or ACK waits. */ + spinlock_t event_lock; + bool stopping; + bool service_up; + /* Software cleanup completed for generation, not firmware readiness. */ + bool reconciled; + u64 generation; struct pmic_glink_altmode_port ports[PMIC_GLINK_MAX_PORTS]; }; -static int pmic_glink_altmode_request(struct pmic_glink_altmode *altmode, u32 cmd, u32 arg) +static bool pmic_glink_altmode_current(struct pmic_glink_altmode *altmode, u64 generation) +{ + unsigned long flags; + bool valid; + + spin_lock_irqsave(&altmode->event_lock, flags); + valid = !altmode->stopping && altmode->service_up && + generation == altmode->generation; + spin_unlock_irqrestore(&altmode->event_lock, flags); + return valid; +} + +static int pmic_glink_altmode_request(struct pmic_glink_altmode *altmode, + u32 cmd, u32 arg, u64 generation) { struct usbc_write_req req = {}; + unsigned long flags; unsigned long left; int ret; @@ -152,19 +185,33 @@ static int pmic_glink_altmode_request(struct pmic_glink_altmode *altmode, u32 cm */ guard(mutex)(&altmode->lock); + if (!pmic_glink_altmode_current(altmode, generation)) + return -ECONNRESET; + + /* Clear stored credits, not late replies arriving after a new request. */ + spin_lock_irqsave(&altmode->event_lock, flags); + reinit_completion(&altmode->pan_ack); + spin_unlock_irqrestore(&altmode->event_lock, flags); + req.hdr.owner = cpu_to_le32(altmode->owner_id); req.hdr.type = cpu_to_le32(PMIC_GLINK_REQ_RESP); req.hdr.opcode = cpu_to_le32(USBC_CMD_WRITE_REQ); req.cmd = cpu_to_le32(cmd); req.arg = cpu_to_le32(arg); - ret = pmic_glink_send(altmode->client, &req, sizeof(req)); + ret = pmic_glink_send_generation(altmode->client, &req, sizeof(req), generation); if (ret) { dev_err(altmode->dev, "failed to send altmode request: %#x (%d)\n", cmd, ret); return ret; } + if (!pmic_glink_altmode_current(altmode, generation)) + return -ECONNRESET; + left = wait_for_completion_timeout(&altmode->pan_ack, 5 * HZ); + /* PDR/stop also wake the waiter; neither wake is a firmware response. */ + if (!pmic_glink_altmode_current(altmode, generation)) + return -ECONNRESET; if (!left) { dev_err(altmode->dev, "timeout waiting for altmode request ack for: %#x\n", cmd); return -ETIMEDOUT; @@ -173,13 +220,46 @@ static int pmic_glink_altmode_request(struct pmic_glink_altmode *altmode, u32 cm return 0; } -static void pmic_glink_altmode_enable_dp(struct pmic_glink_altmode *altmode, +static int pmic_glink_altmode_set_mode(struct pmic_glink_altmode *altmode, + struct pmic_glink_altmode_port *port, + const struct pmic_glink_altmode_event *event, + struct typec_altmode *alt, + unsigned long mode, void *data) +{ + struct typec_mux_state state = { .alt = alt, .mode = mode, .data = data }; + struct typec_retimer_state retimer_state = { .alt = alt, .mode = mode, .data = data }; + int ret; + + lockdep_assert_held(&altmode->lifecycle_lock); + if (!pmic_glink_altmode_current(altmode, event->generation)) + return -ECONNRESET; + + ret = typec_mux_set(port->typec_mux, &state); + if (ret) { + dev_err(altmode->dev, "failed to switch mux on port %u: %d\n", + port->index, ret); + return ret; + } + + if (!pmic_glink_altmode_current(altmode, event->generation)) + return -ECONNRESET; + + ret = typec_retimer_set(port->typec_retimer, &retimer_state); + if (ret) + dev_err(altmode->dev, "failed to set retimer on port %u: %d\n", + port->index, ret); + if (!ret && !pmic_glink_altmode_current(altmode, event->generation)) + return -ECONNRESET; + return ret; +} + +static int pmic_glink_altmode_enable_dp(struct pmic_glink_altmode *altmode, struct pmic_glink_altmode_port *port, + const struct pmic_glink_altmode_event *event, u8 mode, bool hpd_state, bool hpd_irq) { struct typec_displayport_data dp_data = {}; - int ret; dp_data.status = DP_STATUS_ENABLED; if (hpd_state) @@ -188,30 +268,17 @@ static void pmic_glink_altmode_enable_dp(struct pmic_glink_altmode *altmode, dp_data.status |= DP_STATUS_IRQ_HPD; dp_data.conf = DP_CONF_SET_PIN_ASSIGN(mode); - port->state.alt = &port->dp_alt; - port->state.data = &dp_data; - port->state.mode = TYPEC_MODAL_STATE(mode); - - ret = typec_mux_set(port->typec_mux, &port->state); - if (ret) - dev_err(altmode->dev, "failed to switch mux to DP: %d\n", ret); - - port->retimer_state.alt = &port->dp_alt; - port->retimer_state.data = &dp_data; - port->retimer_state.mode = TYPEC_MODAL_STATE(mode); - - ret = typec_retimer_set(port->typec_retimer, &port->retimer_state); - if (ret) - dev_err(altmode->dev, "failed to setup retimer to DP: %d\n", ret); + return pmic_glink_altmode_set_mode(altmode, port, event, &port->dp_alt, + TYPEC_MODAL_STATE(mode), &dp_data); } -static void pmic_glink_altmode_enable_tbt(struct pmic_glink_altmode *altmode, - struct pmic_glink_altmode_port *port) +static int pmic_glink_altmode_enable_tbt(struct pmic_glink_altmode *altmode, + struct pmic_glink_altmode_port *port, + const struct pmic_glink_altmode_event *event) { - struct usbc_sc8280x_tbt_data *tbt = &port->tbt_data; + const struct usbc_sc8280x_tbt_data *tbt = &event->tbt_data; struct typec_thunderbolt_data tbt_data = {}; u32 cable_speed; - int ret; /* Device Discover Mode VDO */ tbt_data.device_mode = TBT_MODE; @@ -246,29 +313,16 @@ static void pmic_glink_altmode_enable_tbt(struct pmic_glink_altmode *altmode, tbt_data.enter_vdo |= TBT_SET_CABLE_ROUNDED(tbt->rounded_cable); } - port->state.alt = &port->tbt_alt; - port->state.data = &tbt_data; - port->state.mode = TYPEC_MODAL_STATE(port->mode); - - ret = typec_mux_set(port->typec_mux, &port->state); - if (ret) - dev_err(altmode->dev, "failed to switch mux to USB: %d\n", ret); - - port->retimer_state.alt = &port->tbt_alt; - port->retimer_state.data = &tbt_data; - port->retimer_state.mode = TYPEC_MODAL_STATE(port->mode); - - ret = typec_retimer_set(port->typec_retimer, &port->retimer_state); - if (ret) - dev_err(altmode->dev, "failed to setup retimer to USB: %d\n", ret); + return pmic_glink_altmode_set_mode(altmode, port, event, &port->tbt_alt, + TYPEC_TBT_MODE, &tbt_data); } -static void pmic_glink_altmode_enable_usb4(struct pmic_glink_altmode *altmode, - struct pmic_glink_altmode_port *port) +static int pmic_glink_altmode_enable_usb4(struct pmic_glink_altmode *altmode, + struct pmic_glink_altmode_port *port, + const struct pmic_glink_altmode_event *event) { - struct usbc_sc8280x_tbt_data *tbt = &port->tbt_data; + const struct usbc_sc8280x_tbt_data *tbt = &event->tbt_data; struct enter_usb_data data = {}; - int ret; data.eudo = FIELD_PREP(EUDO_USB_MODE_MASK, EUDO_USB_MODE_USB4); @@ -284,116 +338,152 @@ static void pmic_glink_altmode_enable_usb4(struct pmic_glink_altmode *altmode, data.eudo |= FIELD_PREP(EUDO_CABLE_TYPE_MASK, tbt->cable_type); - port->state.alt = NULL; - port->state.data = &data; - port->state.mode = TYPEC_MODE_USB4; - - ret = typec_mux_set(port->typec_mux, &port->state); - if (ret) - dev_err(altmode->dev, "failed to switch mux to USB: %d\n", ret); - - port->retimer_state.alt = NULL; - port->retimer_state.data = &data; - port->retimer_state.mode = TYPEC_MODE_USB4; - - ret = typec_retimer_set(port->typec_retimer, &port->retimer_state); - if (ret) - dev_err(altmode->dev, "failed to setup retimer to USB: %d\n", ret); + return pmic_glink_altmode_set_mode(altmode, port, event, NULL, TYPEC_MODE_USB4, &data); } -static void pmic_glink_altmode_enable_usb(struct pmic_glink_altmode *altmode, - struct pmic_glink_altmode_port *port) +static int pmic_glink_altmode_enable_usb(struct pmic_glink_altmode *altmode, + struct pmic_glink_altmode_port *port, + const struct pmic_glink_altmode_event *event) { - int ret; - - port->state.alt = NULL; - port->state.data = NULL; - port->state.mode = TYPEC_STATE_USB; - - ret = typec_mux_set(port->typec_mux, &port->state); - if (ret) - dev_err(altmode->dev, "failed to switch mux to USB: %d\n", ret); - - port->retimer_state.alt = NULL; - port->retimer_state.data = NULL; - port->retimer_state.mode = TYPEC_STATE_USB; - - ret = typec_retimer_set(port->typec_retimer, &port->retimer_state); - if (ret) - dev_err(altmode->dev, "failed to setup retimer to USB: %d\n", ret); + return pmic_glink_altmode_set_mode(altmode, port, event, NULL, TYPEC_STATE_USB, NULL); } -static void pmic_glink_altmode_safe(struct pmic_glink_altmode *altmode, - struct pmic_glink_altmode_port *port) +static int pmic_glink_altmode_safe(struct pmic_glink_altmode *altmode, + struct pmic_glink_altmode_port *port, + const struct pmic_glink_altmode_event *event) { - int ret; - - port->state.alt = NULL; - port->state.data = NULL; - port->state.mode = TYPEC_STATE_SAFE; - - ret = typec_mux_set(port->typec_mux, &port->state); - if (ret) - dev_err(altmode->dev, "failed to switch mux to safe mode: %d\n", ret); - - port->retimer_state.alt = NULL; - port->retimer_state.data = NULL; - port->retimer_state.mode = TYPEC_STATE_SAFE; - - ret = typec_retimer_set(port->typec_retimer, &port->retimer_state); - if (ret) - dev_err(altmode->dev, "failed to setup retimer to USB: %d\n", ret); + return pmic_glink_altmode_set_mode(altmode, port, event, NULL, TYPEC_STATE_SAFE, NULL); } -static void pmic_glink_altmode_worker(struct work_struct *work) +static void pmic_glink_altmode_apply(struct pmic_glink_altmode_port *alt_port, + const struct pmic_glink_altmode_event *event) { - struct pmic_glink_altmode_port *alt_port = work_to_altmode_port(work); struct pmic_glink_altmode *altmode = alt_port->altmode; - enum drm_connector_status conn_status; + bool dp = event->svid == USB_TYPEC_DP_SID; + int ret; + + lockdep_assert_held(&altmode->lifecycle_lock); + if (!pmic_glink_altmode_current(altmode, event->generation)) + return; + + ret = typec_switch_set(alt_port->typec_switch, event->orientation); + if (ret) { + dev_err(altmode->dev, "failed to set orientation on port %u: %d\n", + alt_port->index, ret); + goto out_hpd; + } - typec_switch_set(alt_port->typec_switch, alt_port->orientation); + if (!pmic_glink_altmode_current(altmode, event->generation)) + return; /* * MUX_CTRL_STATE_DP4LN/USB3_DP may only be set if SVID=DP, but we need * to special-case the SVID=DP && mux_ctrl=NO_CONN case to deliver a * HPD notification */ - if (alt_port->svid == USB_TYPEC_DP_SID) { - if (alt_port->mux_ctrl == MUX_CTRL_STATE_NO_CONN) { - pmic_glink_altmode_safe(altmode, alt_port); + if (dp) { + if (event->mux_ctrl == MUX_CTRL_STATE_NO_CONN) { + ret = pmic_glink_altmode_safe(altmode, alt_port, event); } else { - pmic_glink_altmode_enable_dp(altmode, alt_port, - alt_port->mode, - alt_port->hpd_state, - alt_port->hpd_irq); + ret = pmic_glink_altmode_enable_dp(altmode, alt_port, + event, + event->mode, + event->hpd_state, + event->hpd_irq); } - - if (alt_port->hpd_state) - conn_status = connector_status_connected; + } else if (event->mux_ctrl == MUX_CTRL_STATE_TUNNELING) { + if (event->svid == USB_TYPEC_TBT_SID) + ret = pmic_glink_altmode_enable_tbt(altmode, alt_port, event); else - conn_status = connector_status_disconnected; + ret = pmic_glink_altmode_enable_usb4(altmode, alt_port, event); + } else if (event->mux_ctrl == MUX_CTRL_STATE_USB3_ONLY) { + ret = pmic_glink_altmode_enable_usb(altmode, alt_port, event); + } else if (event->mux_ctrl == MUX_CTRL_STATE_NO_CONN) { + ret = pmic_glink_altmode_safe(altmode, alt_port, event); + } else { + dev_err(altmode->dev, "Got unknown mux_ctrl: %u on port %u, forcing safe mode\n", + event->mux_ctrl, alt_port->index); + ret = pmic_glink_altmode_safe(altmode, alt_port, event); + } + +out_hpd: + if (!pmic_glink_altmode_current(altmode, event->generation)) + return; + /* A real disconnect remains meaningful even if hardware programming failed. */ + if ((dp && (!event->hpd_state || + (event->has_mux_ctrl && event->mux_ctrl == MUX_CTRL_STATE_NO_CONN))) || + (event->has_mux_ctrl && event->mux_ctrl == MUX_CTRL_STATE_NO_CONN && + alt_port->hpd_connected)) { drm_aux_hpd_bridge_notify_extra(&alt_port->bridge->dev, - conn_status, - alt_port->hpd_irq ? + connector_status_disconnected, + dp && event->hpd_irq ? DRM_CONNECTOR_DP_IRQ_HPD : DRM_CONNECTOR_NO_EXTRA_STATUS); - } else if (alt_port->mux_ctrl == MUX_CTRL_STATE_TUNNELING) { - if (alt_port->svid == USB_TYPEC_TBT_SID) - pmic_glink_altmode_enable_tbt(altmode, alt_port); - else - pmic_glink_altmode_enable_usb4(altmode, alt_port); - } else if (alt_port->mux_ctrl == MUX_CTRL_STATE_USB3_ONLY) { - pmic_glink_altmode_enable_usb(altmode, alt_port); - } else if (alt_port->mux_ctrl == MUX_CTRL_STATE_NO_CONN) { - pmic_glink_altmode_safe(altmode, alt_port); - } else { - dev_err(altmode->dev, "Got unknown mux_ctrl: %u on port %u, forcing safe mode\n", - alt_port->mux_ctrl, alt_port->index); - pmic_glink_altmode_safe(altmode, alt_port); + alt_port->hpd_connected = false; + } else if (!ret && dp) { + drm_aux_hpd_bridge_notify_extra(&alt_port->bridge->dev, + connector_status_connected, + event->hpd_irq ? + DRM_CONNECTOR_DP_IRQ_HPD : + DRM_CONNECTOR_NO_EXTRA_STATUS); + alt_port->hpd_connected = true; } +} - pmic_glink_altmode_request(altmode, ALTMODE_PAN_ACK, alt_port->index); +static void pmic_glink_altmode_worker(struct work_struct *work) +{ + struct pmic_glink_altmode_port *port = work_to_altmode_port(work); + struct pmic_glink_altmode *altmode = port->altmode; + struct pmic_glink_altmode_event event; + unsigned long flags; + + scoped_guard(mutex, &altmode->lifecycle_lock) { + spin_lock_irqsave(&altmode->event_lock, flags); + if (altmode->stopping || !altmode->service_up || !altmode->reconciled || + !kfifo_get(&port->events, &event)) { + spin_unlock_irqrestore(&altmode->event_lock, flags); + return; + } + spin_unlock_irqrestore(&altmode->event_lock, flags); + + pmic_glink_altmode_apply(port, &event); + } + + /* Other ports/reconciliation may proceed while this request waits. */ + pmic_glink_altmode_request(altmode, ALTMODE_PAN_ACK, port->index, event.generation); + + /* Allow the freezer between events, including when an ACK times out. */ + spin_lock_irqsave(&altmode->event_lock, flags); + if (!altmode->stopping && altmode->service_up && altmode->reconciled && + !kfifo_is_empty(&port->events)) + queue_work(system_freezable_wq, &port->work); + spin_unlock_irqrestore(&altmode->event_lock, flags); +} + +static void pmic_glink_altmode_queue_event(struct pmic_glink_altmode_port *port, + const struct pmic_glink_altmode_event *event) +{ + struct pmic_glink_altmode *altmode = port->altmode; + struct pmic_glink_altmode_event pending = *event; + unsigned long flags; + bool queued; + + spin_lock_irqsave(&altmode->event_lock, flags); + if (altmode->stopping || !altmode->service_up) { + spin_unlock_irqrestore(&altmode->event_lock, flags); + return; + } + pending.generation = altmode->generation; + queued = kfifo_put(&port->events, pending); + if (queued) + queue_work(system_freezable_wq, &port->work); + spin_unlock_irqrestore(&altmode->event_lock, flags); + + /* Never overwrite older events or invent ACK/replay semantics on rejection. */ + if (!queued) + dev_err_ratelimited(altmode->dev, "altmode event queue full on port %u\n", + port->index); } static enum typec_orientation pmic_glink_altmode_orientation(unsigned int orientation) @@ -417,6 +507,7 @@ static void pmic_glink_altmode_sc8180xp_notify(struct pmic_glink_altmode *altmod const void *data, size_t len) { struct pmic_glink_altmode_port *alt_port; + struct pmic_glink_altmode_event event = {}; const struct usbc_sc8180x_notify *msg; u32 notification; u8 orientation; @@ -449,12 +540,13 @@ static void pmic_glink_altmode_sc8180xp_notify(struct pmic_glink_altmode *altmod } alt_port = &altmode->ports[port]; - alt_port->orientation = pmic_glink_altmode_orientation(orientation); - alt_port->svid = svid; - alt_port->mode = mode; - alt_port->hpd_state = hpd_state; - alt_port->hpd_irq = hpd_irq; - queue_work(system_freezable_wq, &alt_port->work); + event.orientation = pmic_glink_altmode_orientation(orientation); + event.svid = svid; + event.mode = mode; + event.hpd_state = hpd_state; + event.hpd_irq = hpd_irq; + /* Do not infer SC8280 mux states from this legacy notification format. */ + pmic_glink_altmode_queue_event(alt_port, &event); } #define SC8280XP_DPAM_MASK 0x3f @@ -465,6 +557,7 @@ static void pmic_glink_altmode_sc8280xp_notify(struct pmic_glink_altmode *altmod u16 svid, const void *data, size_t len) { struct pmic_glink_altmode_port *alt_port; + struct pmic_glink_altmode_event event = {}; const struct usbc_sc8280x_tbt_data *tbt; const struct usbc_sc8280x_dp_data *dp; const struct usbc_notify *notify; @@ -488,30 +581,32 @@ static void pmic_glink_altmode_sc8280xp_notify(struct pmic_glink_altmode *altmod } alt_port = &altmode->ports[port]; - alt_port->orientation = pmic_glink_altmode_orientation(orientation); - alt_port->svid = svid; - alt_port->mux_ctrl = notify->mux_ctrl; + event.orientation = pmic_glink_altmode_orientation(orientation); + event.svid = svid; + event.mux_ctrl = notify->mux_ctrl; + event.has_mux_ctrl = true; if (svid == USB_TYPEC_DP_SID) { dp = ¬ify->extended_data.dp; - alt_port->mode = dp->pin_assignment - DPAM_HPD_A; - alt_port->hpd_state = dp->hpd_state; - alt_port->hpd_irq = dp->hpd_irq; - } else if (alt_port->mux_ctrl == MUX_CTRL_STATE_TUNNELING) { + event.mode = dp->pin_assignment - DPAM_HPD_A; + event.hpd_state = dp->hpd_state; + event.hpd_irq = dp->hpd_irq; + } else if (event.mux_ctrl == MUX_CTRL_STATE_TUNNELING) { /* Valid for both USB4 and TBT3 */ tbt = ¬ify->extended_data.tbt; - alt_port->tbt_data = *tbt; + event.tbt_data = *tbt; } - queue_work(system_freezable_wq, &alt_port->work); + pmic_glink_altmode_queue_event(alt_port, &event); } static void pmic_glink_altmode_callback(const void *data, size_t len, void *priv) { struct pmic_glink_altmode *altmode = priv; const struct pmic_glink_hdr *hdr = data; + unsigned long flags; u16 opcode; u16 svid; @@ -520,7 +615,9 @@ static void pmic_glink_altmode_callback(const void *data, size_t len, void *priv switch (opcode) { case USBC_CMD_WRITE_REQ: + spin_lock_irqsave(&altmode->event_lock, flags); complete(&altmode->pan_ack); + spin_unlock_irqrestore(&altmode->event_lock, flags); break; case USBC_NOTIFY_IND: pmic_glink_altmode_sc8280xp_notify(altmode, svid, data, len); @@ -549,19 +646,108 @@ static void pmic_glink_altmode_put_switch(void *data) static void pmic_glink_altmode_enable_worker(struct work_struct *work) { struct pmic_glink_altmode *altmode = work_to_altmode(work); + struct pmic_glink_altmode_port *port; + unsigned long flags; + unsigned int i; + u64 generation; + bool up; int ret; - ret = pmic_glink_altmode_request(altmode, ALTMODE_PAN_EN, 0); + scoped_guard(mutex, &altmode->lifecycle_lock) { + spin_lock_irqsave(&altmode->event_lock, flags); + /* A coalesced run may already have reconciled the latest generation. */ + if (altmode->stopping || altmode->reconciled) { + spin_unlock_irqrestore(&altmode->event_lock, flags); + return; + } + generation = altmode->generation; + up = altmode->service_up; + spin_unlock_irqrestore(&altmode->event_lock, flags); + + /* An old in-flight publication finishes before this reconciliation. */ + for (i = 0; i < ARRAY_SIZE(altmode->ports); i++) { + port = &altmode->ports[i]; + if (!port->altmode) + continue; + if (port->hpd_connected) + drm_aux_hpd_bridge_notify(&port->bridge->dev, + connector_status_disconnected); + port->hpd_connected = false; + } + + spin_lock_irqsave(&altmode->event_lock, flags); + if (!altmode->stopping && up && altmode->service_up && + generation == altmode->generation) { + /* DOWN stays closed; a matching UP cleanup opens the barrier. */ + altmode->reconciled = true; + for (i = 0; i < ARRAY_SIZE(altmode->ports); i++) { + port = &altmode->ports[i]; + if (port->altmode && !kfifo_is_empty(&port->events)) + queue_work(system_freezable_wq, &port->work); + } + } + spin_unlock_irqrestore(&altmode->event_lock, flags); + } + + if (!up) + return; + + /* Reconciled is software state, not a dependency on a PAN_EN reply. */ + ret = pmic_glink_altmode_request(altmode, ALTMODE_PAN_EN, 0, generation); if (ret) - dev_err(altmode->dev, "failed to request altmode notifications: %d\n", ret); + dev_err(altmode->dev, "failed to request altmode notifications, generation %llu: %d\n", + (unsigned long long)generation, ret); } static void pmic_glink_altmode_pdr_notify(void *priv, int state) { struct pmic_glink_altmode *altmode = priv; + u64 generation = pmic_glink_get_generation(altmode->client); + bool up = state == SERVREG_SERVICE_STATE_UP; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&altmode->event_lock, flags); + if (!altmode->stopping && + (generation != altmode->generation || up != altmode->service_up)) { + altmode->generation = generation; + altmode->service_up = up; + altmode->reconciled = false; + for (i = 0; i < ARRAY_SIZE(altmode->ports); i++) { + if (altmode->ports[i].altmode) + kfifo_reset(&altmode->ports[i].events); + } + /* Wake a waiter to reject this generation change, not to ACK it. */ + complete(&altmode->pan_ack); + queue_work(system_freezable_wq, &altmode->enable_work); + } + spin_unlock_irqrestore(&altmode->event_lock, flags); +} - if (state == SERVREG_SERVICE_STATE_UP) - schedule_work(&altmode->enable_work); +static void pmic_glink_altmode_stop(void *data) +{ + struct pmic_glink_altmode *altmode = data; + struct pmic_glink_altmode_port *port; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&altmode->event_lock, flags); + altmode->stopping = true; + /* The post-send/wait state checks turn this local wake into cancellation. */ + complete(&altmode->pan_ack); + spin_unlock_irqrestore(&altmode->event_lock, flags); + + /* Keep the registered client alive so an in-flight ACK can complete. */ + cancel_work_sync(&altmode->enable_work); + for (i = 0; i < ARRAY_SIZE(altmode->ports); i++) { + port = &altmode->ports[i]; + if (!port->altmode) + continue; + cancel_work_sync(&port->work); + spin_lock_irqsave(&altmode->event_lock, flags); + kfifo_reset(&port->events); + spin_unlock_irqrestore(&altmode->event_lock, flags); + } } static const struct of_device_id pmic_glink_altmode_of_quirks[] = { @@ -595,6 +781,8 @@ static int pmic_glink_altmode_probe(struct auxiliary_device *adev, INIT_WORK(&altmode->enable_work, pmic_glink_altmode_enable_worker); init_completion(&altmode->pan_ack); mutex_init(&altmode->lock); + mutex_init(&altmode->lifecycle_lock); + spin_lock_init(&altmode->event_lock); device_for_each_child_node(dev, fwnode) { ret = fwnode_property_read_u32(fwnode, "reg", &port); @@ -619,6 +807,7 @@ static int pmic_glink_altmode_probe(struct auxiliary_device *adev, alt_port->altmode = altmode; alt_port->index = port; INIT_WORK(&alt_port->work, pmic_glink_altmode_worker); + INIT_KFIFO(alt_port->events); alt_port->bridge = devm_drm_dp_hpd_bridge_alloc(dev, to_of_node(fwnode)); if (IS_ERR(alt_port->bridge)) { @@ -698,6 +887,11 @@ static int pmic_glink_altmode_probe(struct auxiliary_device *adev, if (IS_ERR(altmode->client)) return PTR_ERR(altmode->client); + /* Runs before client release and before Type-C/DRM providers are released. */ + ret = devm_add_action_or_reset(dev, pmic_glink_altmode_stop, altmode); + if (ret) + return ret; + pmic_glink_client_register(altmode->client); return 0; diff --git a/drivers/usb/typec/mux/ps883x.c b/drivers/usb/typec/mux/ps883x.c index 2ec57cfe748a9c..333073ff235273 100644 --- a/drivers/usb/typec/mux/ps883x.c +++ b/drivers/usb/typec/mux/ps883x.c @@ -79,6 +79,7 @@ struct ps883x_retimer { struct mutex lock; /* protect non-concurrent retimer & switch */ enum typec_orientation orientation; + bool orientation_valid; bool in_reset; bool disable_usb4; }; @@ -171,6 +172,7 @@ static void ps883x_reset(struct ps883x_retimer *retimer) gpiod_set_value(retimer->reset_gpio, 1); ps883x_disable_vregs(retimer); + retimer->orientation_valid = false; retimer->in_reset = true; } @@ -199,9 +201,11 @@ static int ps883x_configure(struct ps883x_retimer *retimer, int cfg0, ret = regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_0, cfg0); if (ret) { + retimer->orientation_valid = false; dev_err(dev, "failed to write conn_status_0: %d\n", ret); return ret; } + retimer->orientation_valid = true; ret = regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_1, cfg1); if (ret) { @@ -322,22 +326,29 @@ static int ps883x_sw_set(struct typec_switch_dev *sw, guard(mutex)(&retimer->lock); - if (retimer->orientation != orientation) { - retimer->orientation = orientation; - + if (retimer->orientation != orientation || !retimer->orientation_valid) { /* * Orientation notifications usually come prior to mode switch * events. If the retimer is already in reset, we still want to * cache the new orientation value for the subsequent ps883x_set(). */ - if (retimer->in_reset) + if (retimer->in_reset) { + retimer->orientation = orientation; + retimer->orientation_valid = false; return 0; + } ret = regmap_assign_bits(retimer->regmap, REG_USB_PORT_CONN_STATUS_0, CONN_STATUS_0_ORIENTATION_REVERSED, orientation == TYPEC_ORIENTATION_REVERSE); - if (ret) + if (ret) { + retimer->orientation_valid = false; dev_err(&retimer->client->dev, "failed to set orientation: %d\n", ret); + return ret; + } + + retimer->orientation = orientation; + retimer->orientation_valid = true; } return ret; @@ -532,8 +543,10 @@ static int ps883x_retimer_probe(struct i2c_client *client) err_clk_disable: clk_disable_unprepare(retimer->xo_clk); err_vregs_disable: - gpiod_set_value(retimer->reset_gpio, 1); - ps883x_disable_vregs(retimer); + /* Initial reset may already have released the regulator enables. */ + mutex_lock(&retimer->lock); + ps883x_reset(retimer); + mutex_unlock(&retimer->lock); err_mux_put: typec_mux_put(retimer->typec_mux); err_switch_put: @@ -549,12 +562,12 @@ static void ps883x_retimer_remove(struct i2c_client *client) typec_retimer_unregister(retimer->retimer); typec_switch_unregister(retimer->sw); - gpiod_set_value(retimer->reset_gpio, 1); + mutex_lock(&retimer->lock); + ps883x_reset(retimer); + mutex_unlock(&retimer->lock); clk_disable_unprepare(retimer->xo_clk); - ps883x_disable_vregs(retimer); - typec_mux_put(retimer->typec_mux); typec_switch_put(retimer->typec_switch); } diff --git a/include/linux/soc/qcom/pmic_glink.h b/include/linux/soc/qcom/pmic_glink.h index 7cddf10277528e..76b1519a4d691f 100644 --- a/include/linux/soc/qcom/pmic_glink.h +++ b/include/linux/soc/qcom/pmic_glink.h @@ -22,6 +22,9 @@ struct pmic_glink_hdr { }; int pmic_glink_send(struct pmic_glink_client *client, void *data, size_t len); +u64 pmic_glink_get_generation(struct pmic_glink_client *client); +int pmic_glink_send_generation(struct pmic_glink_client *client, void *data, + size_t len, u64 generation); struct pmic_glink_client *devm_pmic_glink_client_alloc(struct device *dev, unsigned int id,