From 323429e2402900823f830820892aab4b45ed417a Mon Sep 17 00:00:00 2001 From: Cho KyongHo Date: Wed, 22 Jul 2015 23:42:02 +0900 Subject: [PATCH] [COMMON] media: smfc: use s/w timer if HWFC is enabled If HWFC is enabled, the internal timer in the H/W is not available because when the H/W finishes depends on the outside of SMFC itself. Instead of the internal timer, S/W timer is used for the case of HWFC. S/W timer is expired if the H/W is not completed in a second that is quite huge amount of time. Change-Id: I0a23448033b91bcb64523cebfeeee14d78c0f410 Signed-off-by: Cho KyongHo --- drivers/media/platform/exynos/smfc/smfc.c | 80 ++++++++++++++++++++++- drivers/media/platform/exynos/smfc/smfc.h | 7 ++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/exynos/smfc/smfc.c b/drivers/media/platform/exynos/smfc/smfc.c index 2722162fb7e3..9c2c9992cbad 100644 --- a/drivers/media/platform/exynos/smfc/smfc.c +++ b/drivers/media/platform/exynos/smfc/smfc.c @@ -302,14 +302,25 @@ static irqreturn_t exynos_smfc_irq_handler(int irq, void *priv) ktime = ktime_get(); + spin_lock(&smfc->flag_lock); + + if (!!(smfc->flags & SMFC_DEV_TIMEDOUT)) { + /* The tieout handler does the rest */ + dev_err(smfc->dev, "Interrupt occurred after timed-out.\n"); + spin_unlock(&smfc->flag_lock); + return IRQ_HANDLED; + } + if (!(smfc->flags & SMFC_DEV_RUNNING)) { smfc_dump_registers(smfc); BUG(); } - spin_lock(&smfc->flag_lock); suspending = !!(smfc->flags & SMFC_DEV_SUSPENDING); - smfc->flags &= ~SMFC_DEV_RUNNING; + if (!!(smfc->flags & SMFC_DEV_OTF_EMUMODE)) + del_timer(&smfc->timer); + smfc->flags &= ~(SMFC_DEV_RUNNING | SMFC_DEV_OTF_EMUMODE); + spin_unlock(&smfc->flag_lock); if (!smfc_hwstatus_okay(smfc, ctx)) { @@ -369,6 +380,60 @@ static irqreturn_t exynos_smfc_irq_handler(int irq, void *priv) return IRQ_HANDLED; } +static void smfc_timedout_handler(unsigned long arg) +{ + struct smfc_dev *smfc = (struct smfc_dev *)arg; + struct smfc_ctx *ctx; + unsigned long flags; + bool suspending; + + spin_lock_irqsave(&smfc->flag_lock, flags); + if (!(smfc->flags & SMFC_DEV_RUNNING)) { + /* Interrupt is occurred before timer handler is called */ + spin_unlock_irqrestore(&smfc->flag_lock, flags); + return; + } + + /* timer is enabled only when HWFC is enabled */ + BUG_ON(!(smfc->flags & SMFC_DEV_OTF_EMUMODE)); + suspending = !!(smfc->flags & SMFC_DEV_SUSPENDING); + smfc->flags |= SMFC_DEV_TIMEDOUT; /* indicate the timedout is handled */ + smfc->flags &= ~(SMFC_DEV_RUNNING | SMFC_DEV_OTF_EMUMODE); + spin_unlock_irqrestore(&smfc->flag_lock, flags); + + dev_err(smfc->dev, "=== TIMED-OUT! (1 sec.) ========================="); + smfc_dump_registers(smfc); + smfc_hwconfigure_reset(smfc); + + if (!IS_ERR(smfc->clk_gate)) { + clk_disable(smfc->clk_gate); + if (!IS_ERR(smfc->clk_gate2)) + clk_disable(smfc->clk_gate2); + } + + pm_runtime_put(smfc->dev); + + ctx = v4l2_m2m_get_curr_priv(smfc->m2mdev); + if (ctx) { + v4l2_m2m_buf_done(v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx), + VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx), + VB2_BUF_STATE_ERROR); + if (!suspending) { + v4l2_m2m_job_finish(smfc->m2mdev, ctx->fh.m2m_ctx); + } else { + spin_lock(&smfc->flag_lock); + spin_unlock(&smfc->flag_lock); + } + } + + spin_lock_irqsave(&smfc->flag_lock, flags); + /* finished timedout handling and suspend() can return */ + smfc->flags &= ~(SMFC_DEV_TIMEDOUT | SMFC_DEV_SUSPENDING); + spin_unlock_irqrestore(&smfc->flag_lock, flags); +} + + static int smfc_vb2_queue_setup(struct vb2_queue *vq, unsigned int *num_buffers, unsigned int *num_planes, unsigned int sizes[], struct device *alloc_devs[]) @@ -1210,8 +1275,17 @@ static void smfc_m2m_device_run(void *priv) spin_lock_irqsave(&ctx->smfc->flag_lock, flags); ctx->smfc->flags |= SMFC_DEV_RUNNING; + if (!!enable_hwfc) + ctx->smfc->flags |= SMFC_DEV_OTF_EMUMODE; spin_unlock_irqrestore(&ctx->smfc->flag_lock, flags); + /* + * SMFC internal timer is unavailable if HWFC is enabled + * Therefore, S/W timer object is used to detect unexpected delay. + */ + if (!!enable_hwfc) + mod_timer(&ctx->smfc->timer, jiffies + HZ); /* 1 sec. */ + ctx->ktime_beg = ktime_get(); smfc_hwconfigure_start(ctx, restart_interval, !!enable_hwfc); @@ -1446,6 +1520,8 @@ static int exynos_smfc_probe(struct platform_device *pdev) if (ret < 0) return ret; + setup_timer(&smfc->timer, smfc_timedout_handler, (unsigned long)smfc); + platform_set_drvdata(pdev, smfc); spin_lock_init(&smfc->flag_lock); diff --git a/drivers/media/platform/exynos/smfc/smfc.h b/drivers/media/platform/exynos/smfc/smfc.h index 1494b3d0a31f..d6778d73151e 100644 --- a/drivers/media/platform/exynos/smfc/smfc.h +++ b/drivers/media/platform/exynos/smfc/smfc.h @@ -43,8 +43,14 @@ static inline bool is_jpeg(const struct smfc_image_format *fmt) return fmt->bpp_buf[0] == 0; } +/* Set when H/W starts, cleared in irq/timeout handler */ #define SMFC_DEV_RUNNING (1 << 0) +/* Set when suspend handler is called, cleared before irq handler returns. */ #define SMFC_DEV_SUSPENDING (1 << 1) +/* Set when timeout handler is called, cleared before the handler returns. */ +#define SMFC_DEV_TIMEDOUT (1 << 3) +/* Set if HWFC is enabled in device_run, cleared in irq/timeout handler */ +#define SMFC_DEV_OTF_EMUMODE (1 << 4) struct smfc_dev { struct v4l2_device v4l2_dev; @@ -54,6 +60,7 @@ struct smfc_dev { void __iomem *reg; spinlock_t flag_lock; struct mutex video_device_mutex; + struct timer_list timer; int device_id; u32 hwver; u32 flags; -- 2.20.1