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,