diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c index f6728c491cc5..8da9eb3832dd 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() needs drm_drv_uses_atomic_modeset() */ #include #include #include @@ -158,6 +161,9 @@ struct sn65dsi83 { bool lvds_dual_link_even_odd_swap; int lvds_vod_swing_conf[2]; int lvds_term_conf[2]; + int irq; + struct delayed_work monitor_work; + struct work_struct reset_work; }; static const struct regmap_range sn65dsi83_readable_ranges[] = { @@ -362,6 +368,106 @@ static u8 sn65dsi83_get_dsi_div(struct sn65dsi83 *ctx) return dsi_div - 1; } +static int sn65dsi83_reset_pipe(struct sn65dsi83 *sn65dsi83) +{ + struct drm_device *dev = sn65dsi83->bridge.dev; + struct drm_modeset_acquire_ctx ctx; + int err; + + /* + * Reset active outputs of the related CRTC. + * + * This way, drm core will reconfigure each components in the CRTC + * outputs path. In our case, this will force the previous component to + * go back in LP11 mode and so allow the reconfiguration of SN65DSI83 + * bridge. + * + * Keep the lock during the whole operation to be atomic. + */ + + DRM_MODESET_LOCK_ALL_BEGIN(dev, ctx, 0, err); + + if (!sn65dsi83->bridge.encoder->crtc) { + /* + * No CRTC attached -> No CRTC active outputs to reset + * This can happen when the SN65DSI83 is reset. Simply do + * nothing without returning any errors. + */ + err = 0; + goto end; + } + + dev_warn(sn65dsi83->dev, "reset the pipe\n"); + + err = drm_atomic_helper_reset_crtc(sn65dsi83->bridge.encoder->crtc, &ctx); + +end: + DRM_MODESET_LOCK_ALL_END(dev, ctx, err); + + return err; +} + +static void sn65dsi83_reset_work(struct work_struct *ws) +{ + struct sn65dsi83 *ctx = container_of(ws, struct sn65dsi83, reset_work); + int ret; + + /* Reset the pipe */ + ret = sn65dsi83_reset_pipe(ctx); + if (ret) { + dev_err(ctx->dev, "reset pipe failed %pe\n", ERR_PTR(ret)); + return; + } + if (ctx->irq) + enable_irq(ctx->irq); +} + +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) { + /* + * IRQ acknowledged is not always possible (the bridge can be in + * a state where it doesn't answer anymore). To prevent an + * interrupt storm, disable interrupt. The interrupt will be + * after the reset. + */ + if (ctx->irq) + disable_irq_nosync(ctx->irq); + + 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) { @@ -548,6 +654,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->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, @@ -556,6 +671,15 @@ static void sn65dsi83_atomic_disable(struct drm_bridge *bridge, struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge); int ret; + if (ctx->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); @@ -805,6 +929,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); @@ -818,6 +950,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) @@ -842,6 +976,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) { + ctx->irq = client->irq; + ret = devm_request_threaded_irq(ctx->dev, ctx->irq, NULL, sn65dsi83_irq, + IRQF_ONESHOT, dev_name(ctx->dev), ctx); + if (ret) + return dev_err_probe(dev, ret, "failed to request irq\n"); + } + dev_set_drvdata(dev, ctx); i2c_set_clientdata(client, ctx);