From patchwork Thu Oct 24 09:55:38 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Herve Codina X-Patchwork-Id: 13848700 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id B8C53CFA456 for ; Thu, 24 Oct 2024 10:02:39 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 26A0A10E8EA; Thu, 24 Oct 2024 10:02:37 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=bootlin.com header.i=@bootlin.com header.b="VbCsqeHF"; dkim-atps=neutral Received: from mslow1.mail.gandi.net (mslow1.mail.gandi.net [217.70.178.240]) by gabe.freedesktop.org (Postfix) with ESMTPS id E5C9F10E903 for ; Thu, 24 Oct 2024 10:02:34 +0000 (UTC) Received: from relay2-d.mail.gandi.net (unknown [IPv6:2001:4b98:dc4:8::222]) by mslow1.mail.gandi.net (Postfix) with ESMTP id 9B477C3629 for ; Thu, 24 Oct 2024 09:55:47 +0000 (UTC) Received: by mail.gandi.net (Postfix) with ESMTPA id E5EC140010; Thu, 24 Oct 2024 09:55:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=gm1; t=1729763745; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=nUQXtdTvn9UVSZH4EkPi10r3WhhKAUdc+B9p/wH61yw=; b=VbCsqeHFuQuz6k8aevTlfEhl56CY4MWWNhlHmsvB6VkN8iPVhuEauYY+AZNnirOHwO0Apv AUNnXTHt0tgm7OHwmuyfKOV47SEzDT9JD1flKPQ7VZ3LX/bitFkSeIUkM91wEQMfgEr1zz cF4PQltKsnh0jX9Yq91L1cfjam1tm9zy6SEWHjjUdq8yeT8SxIIctrUrZ5+dTlD4pLUYnr LUjoEsHYcRjW5hBVYm5srZIZI6QPxtO+0H/gAR2tSzT8UgZmoyy/Hq0i8PETZn/zl0+qlo /6wbDUbZLbZfeTEOV6DIj64Xkk9kMklqbn2fD+j/hknCeCsZy0sGHMXmCC+rKA== From: Herve Codina To: Andrzej Hajda , Neil Armstrong , Robert Foss , Laurent Pinchart , Jonas Karlman , Jernej Skrabec , David Airlie , Simona Vetter , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Marek Vasut Cc: dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, Luca Ceresoli , Thomas Petazzoni , Herve Codina Subject: [PATCH 2/2] drm: bridge: ti-sn65dsi83: Add error recovery mechanism Date: Thu, 24 Oct 2024 11:55:38 +0200 Message-ID: <20241024095539.1637280-3-herve.codina@bootlin.com> X-Mailer: git-send-email 2.46.2 In-Reply-To: <20241024095539.1637280-1-herve.codina@bootlin.com> References: <20241024095539.1637280-1-herve.codina@bootlin.com> MIME-Version: 1.0 X-GND-Sasl: herve.codina@bootlin.com X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" In some cases observed during ESD tests, the TI SN65DSI83 cannot recover from errors by itself. A full restart of the bridge is needed in those cases to have the bridge output LVDS signals again. The TI SN65DSI83 has some error detection capabilities. Introduce an error recovery mechanism based on this detection. The errors detected are signaled through an interrupt. On system where this interrupt is not available, the driver uses a polling monitoring fallback to check for errors. When an error is present, the recovery process is launched. Restarting the bridge needs to redo the initialization sequence. This initialization sequence has to be done with the DSI data lanes driven in LP11 state. In order to do that, the recovery process resets the entire pipeline. Signed-off-by: Herve Codina --- drivers/gpu/drm/bridge/ti-sn65dsi83.c | 128 ++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c index 96e829163d87..22975b60e80f 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c @@ -35,9 +35,12 @@ #include #include #include +#include +#include #include #include +#include /* DRM_MODESET_LOCK_ALL_BEGIN() need drm_drv_uses_atomic_modeset() */ #include #include #include @@ -147,6 +150,9 @@ struct sn65dsi83 { struct regulator *vcc; bool lvds_dual_link; bool lvds_dual_link_even_odd_swap; + bool use_irq; + struct delayed_work monitor_work; + struct work_struct reset_work; }; static const struct regmap_range sn65dsi83_readable_ranges[] = { @@ -321,6 +327,92 @@ static u8 sn65dsi83_get_dsi_div(struct sn65dsi83 *ctx) return dsi_div - 1; } +static int sn65dsi83_reset_pipeline(struct sn65dsi83 *sn65dsi83) +{ + struct drm_device *dev = sn65dsi83->bridge.dev; + struct drm_modeset_acquire_ctx ctx; + struct drm_atomic_state *state; + int err; + + /* Use operation done in drm_atomic_helper_suspend() followed by + * operation done in drm_atomic_helper_resume() but without releasing + * the lock between suspend()/resume() + */ + + DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, err); + + state = drm_atomic_helper_duplicate_state(dev, &ctx); + if (IS_ERR(state)) { + err = PTR_ERR(state); + goto unlock; + } + + err = drm_atomic_helper_disable_all(dev, &ctx); + if (err < 0) + goto unlock; + + drm_mode_config_reset(dev); + + err = drm_atomic_helper_commit_duplicated_state(state, &ctx); + +unlock: + DRM_MODESET_LOCK_ALL_END(dev, ctx, err); + if (!IS_ERR(state)) + drm_atomic_state_put(state); + return err; +} + +static void sn65dsi83_reset_work(struct work_struct *ws) +{ + struct sn65dsi83 *ctx = container_of(ws, struct sn65dsi83, reset_work); + int ret; + + dev_warn(ctx->dev, "reset pipeline\n"); + + /* Reset the pipeline */ + ret = sn65dsi83_reset_pipeline(ctx); + if (ret) { + dev_err(ctx->dev, "reset pipeline failed %pe\n", ERR_PTR(ret)); + return; + } +} + +static void sn65dsi83_handle_errors(struct sn65dsi83 *ctx) +{ + unsigned int irq_stat; + int ret; + + /* + * Schedule a reset in case of: + * - the bridge doesn't answer + * - the bridge signals an error + */ + + ret = regmap_read(ctx->regmap, REG_IRQ_STAT, &irq_stat); + if (ret || irq_stat) + schedule_work(&ctx->reset_work); +} + +static void sn65dsi83_monitor_work(struct work_struct *work) +{ + struct sn65dsi83 *ctx = container_of(to_delayed_work(work), + struct sn65dsi83, monitor_work); + + sn65dsi83_handle_errors(ctx); + + schedule_delayed_work(&ctx->monitor_work, msecs_to_jiffies(1000)); +} + +static void sn65dsi83_monitor_start(struct sn65dsi83 *ctx) +{ + schedule_delayed_work(&ctx->monitor_work, msecs_to_jiffies(1000)); +} + +static void sn65dsi83_monitor_stop(struct sn65dsi83 *ctx) +{ + cancel_delayed_work_sync(&ctx->monitor_work); +} + static void sn65dsi83_atomic_pre_enable(struct drm_bridge *bridge, struct drm_bridge_state *old_bridge_state) { @@ -509,6 +601,15 @@ static void sn65dsi83_atomic_enable(struct drm_bridge *bridge, regmap_read(ctx->regmap, REG_IRQ_STAT, &pval); if (pval) dev_err(ctx->dev, "Unexpected link status 0x%02x\n", pval); + + if (ctx->use_irq) { + /* Enable irq to detect errors */ + regmap_write(ctx->regmap, REG_IRQ_GLOBAL, REG_IRQ_GLOBAL_IRQ_EN); + regmap_write(ctx->regmap, REG_IRQ_EN, 0xff); + } else { + /* Use the polling task */ + sn65dsi83_monitor_start(ctx); + } } static void sn65dsi83_atomic_disable(struct drm_bridge *bridge, @@ -517,6 +618,15 @@ static void sn65dsi83_atomic_disable(struct drm_bridge *bridge, struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge); int ret; + if (ctx->use_irq) { + /* Disable irq */ + regmap_write(ctx->regmap, REG_IRQ_EN, 0x0); + regmap_write(ctx->regmap, REG_IRQ_GLOBAL, 0x0); + } else { + /* Stop the polling task */ + sn65dsi83_monitor_stop(ctx); + } + /* Put the chip in reset, pull EN line low, and assure 10ms reset low timing. */ gpiod_set_value_cansleep(ctx->enable_gpio, 0); usleep_range(10000, 11000); @@ -673,6 +783,14 @@ static int sn65dsi83_host_attach(struct sn65dsi83 *ctx) return 0; } +static irqreturn_t sn65dsi83_irq(int irq, void *data) +{ + struct sn65dsi83 *ctx = data; + + sn65dsi83_handle_errors(ctx); + return IRQ_HANDLED; +} + static int sn65dsi83_probe(struct i2c_client *client) { const struct i2c_device_id *id = i2c_client_get_device_id(client); @@ -686,6 +804,8 @@ static int sn65dsi83_probe(struct i2c_client *client) return -ENOMEM; ctx->dev = dev; + INIT_WORK(&ctx->reset_work, sn65dsi83_reset_work); + INIT_DELAYED_WORK(&ctx->monitor_work, sn65dsi83_monitor_work); if (dev->of_node) { model = (enum sn65dsi83_model)(uintptr_t) @@ -710,6 +830,14 @@ static int sn65dsi83_probe(struct i2c_client *client) if (IS_ERR(ctx->regmap)) return dev_err_probe(dev, PTR_ERR(ctx->regmap), "failed to get regmap\n"); + if (client->irq) { + ret = devm_request_threaded_irq(ctx->dev, client->irq, NULL, sn65dsi83_irq, + IRQF_ONESHOT, dev_name(ctx->dev), ctx); + if (ret) + return dev_err_probe(dev, ret, "failed to request irq\n"); + ctx->use_irq = true; + } + dev_set_drvdata(dev, ctx); i2c_set_clientdata(client, ctx);