Re: [PATCH V3] drm/bridge: adv7511: Fix Intermittent EDID failures
From: Adam Ford
Date: Mon Jul 08 2024 - 08:37:14 EST
On Sun, Jun 30, 2024 at 5:19 PM Adam Ford <aford173@xxxxxxxxx> wrote:
>
> In the process of adding support for shared IRQ pins, a scenario
> was accidentally created where adv7511_irq_process returned
> prematurely causing the EDID to fail randomly.
>
> Since the interrupt handler is broken up into two main helper functions,
> update both of them to treat the helper functions as IRQ handlers. These
> IRQ routines process their respective tasks as before, but if they
> determine that actual work was done, mark the respective IRQ status
> accordingly, and delay the check until everything has been processed.
>
> This should guarantee the helper functions don't return prematurely
> while still returning proper values of either IRQ_HANDLED or IRQ_NONE.
>
> Reported-by: Liu Ying <victor.liu@xxxxxxx>
> Fixes: f3d9683346d6 ("drm/bridge: adv7511: Allow IRQ to share GPIO pins")
> Signed-off-by: Adam Ford <aford173@xxxxxxxxx>
> Tested-by: Liu Ying <victor.liu@xxxxxxx> # i.MX8MP EVK ADV7535 EDID retrieval w/o IRQ
> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxx>
Gentle nudge on this. I was hoping it could be merged in the 6.10
window since it fixes a known regression, but I know it's summer in
the northern hemisphere, and everyone is busy.
adam
> ---
> V3: Remove unnecessary declaration of ret by evaluating the return
> code of regmap_read directly.
>
> V2: Fix uninitialized cec_status
> Cut back a little on error handling to return either IRQ_NONE or
> IRQ_HANDLED.
>
> diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511.h b/drivers/gpu/drm/bridge/adv7511/adv7511.h
> index ea271f62b214..ec0b7f3d889c 100644
> --- a/drivers/gpu/drm/bridge/adv7511/adv7511.h
> +++ b/drivers/gpu/drm/bridge/adv7511/adv7511.h
> @@ -401,7 +401,7 @@ struct adv7511 {
>
> #ifdef CONFIG_DRM_I2C_ADV7511_CEC
> int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511);
> -void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1);
> +int adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1);
> #else
> static inline int adv7511_cec_init(struct device *dev, struct adv7511 *adv7511)
> {
> diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
> index 44451a9658a3..2e9c88a2b5ed 100644
> --- a/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
> +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_cec.c
> @@ -119,7 +119,7 @@ static void adv7511_cec_rx(struct adv7511 *adv7511, int rx_buf)
> cec_received_msg(adv7511->cec_adap, &msg);
> }
>
> -void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
> +int adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
> {
> unsigned int offset = adv7511->info->reg_cec_offset;
> const u32 irq_tx_mask = ADV7511_INT1_CEC_TX_READY |
> @@ -131,16 +131,19 @@ void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
> unsigned int rx_status;
> int rx_order[3] = { -1, -1, -1 };
> int i;
> + int irq_status = IRQ_NONE;
>
> - if (irq1 & irq_tx_mask)
> + if (irq1 & irq_tx_mask) {
> adv_cec_tx_raw_status(adv7511, irq1);
> + irq_status = IRQ_HANDLED;
> + }
>
> if (!(irq1 & irq_rx_mask))
> - return;
> + return irq_status;
>
> if (regmap_read(adv7511->regmap_cec,
> ADV7511_REG_CEC_RX_STATUS + offset, &rx_status))
> - return;
> + return irq_status;
>
> /*
> * ADV7511_REG_CEC_RX_STATUS[5:0] contains the reception order of RX
> @@ -172,6 +175,8 @@ void adv7511_cec_irq_process(struct adv7511 *adv7511, unsigned int irq1)
>
> adv7511_cec_rx(adv7511, rx_buf);
> }
> +
> + return IRQ_HANDLED;
> }
>
> static int adv7511_cec_adap_enable(struct cec_adapter *adap, bool enable)
> diff --git a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
> index 66ccb61e2a66..c8d2c4a157b2 100644
> --- a/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
> +++ b/drivers/gpu/drm/bridge/adv7511/adv7511_drv.c
> @@ -469,6 +469,8 @@ static int adv7511_irq_process(struct adv7511 *adv7511, bool process_hpd)
> {
> unsigned int irq0, irq1;
> int ret;
> + int cec_status = IRQ_NONE;
> + int irq_status = IRQ_NONE;
>
> ret = regmap_read(adv7511->regmap, ADV7511_REG_INT(0), &irq0);
> if (ret < 0)
> @@ -478,29 +480,31 @@ static int adv7511_irq_process(struct adv7511 *adv7511, bool process_hpd)
> if (ret < 0)
> return ret;
>
> - /* If there is no IRQ to handle, exit indicating no IRQ data */
> - if (!(irq0 & (ADV7511_INT0_HPD | ADV7511_INT0_EDID_READY)) &&
> - !(irq1 & ADV7511_INT1_DDC_ERROR))
> - return -ENODATA;
> -
> regmap_write(adv7511->regmap, ADV7511_REG_INT(0), irq0);
> regmap_write(adv7511->regmap, ADV7511_REG_INT(1), irq1);
>
> - if (process_hpd && irq0 & ADV7511_INT0_HPD && adv7511->bridge.encoder)
> + if (process_hpd && irq0 & ADV7511_INT0_HPD && adv7511->bridge.encoder) {
> schedule_work(&adv7511->hpd_work);
> + irq_status = IRQ_HANDLED;
> + }
>
> if (irq0 & ADV7511_INT0_EDID_READY || irq1 & ADV7511_INT1_DDC_ERROR) {
> adv7511->edid_read = true;
>
> if (adv7511->i2c_main->irq)
> wake_up_all(&adv7511->wq);
> + irq_status = IRQ_HANDLED;
> }
>
> #ifdef CONFIG_DRM_I2C_ADV7511_CEC
> - adv7511_cec_irq_process(adv7511, irq1);
> + cec_status = adv7511_cec_irq_process(adv7511, irq1);
> #endif
>
> - return 0;
> + /* If there is no IRQ to handle, exit indicating no IRQ data */
> + if (irq_status == IRQ_HANDLED || cec_status == IRQ_HANDLED)
> + return IRQ_HANDLED;
> +
> + return IRQ_NONE;
> }
>
> static irqreturn_t adv7511_irq_handler(int irq, void *devid)
> @@ -509,7 +513,7 @@ static irqreturn_t adv7511_irq_handler(int irq, void *devid)
> int ret;
>
> ret = adv7511_irq_process(adv7511, true);
> - return ret < 0 ? IRQ_NONE : IRQ_HANDLED;
> + return ret < 0 ? IRQ_NONE : ret;
> }
>
> /* -----------------------------------------------------------------------------
> --
> 2.43.0
>