From 2b608aa4c4c65916aa87aa787ca568c17babec96 Mon Sep 17 00:00:00 2001 From: Alan Kwong Date: Sun, 24 Jul 2016 22:07:41 -0400 Subject: [PATCH] msm: sde: Correct rotator irq handler to acknowledge irq once Current handler acknowledges interrupt in both the irq handler and the irq thread. This clears interrupts that come between the irq handler and the irq thread. This patch reorganizes irq acknowledgment to the irq handler and changes the irq thread to also check for completed requests by comparing request timestamp and hardware completion timestamp. Upon error or timeout event, hardware status is dumped to aid irq related fault isolation. CRs-Fixed: 1044128 Change-Id: I085c2c2ec55106a8fb196f3ef1cfcb42e2b2507c Signed-off-by: Alan Kwong --- .../platform/msm/sde/rotator/sde_rotator_r3.c | 212 ++++++++++++++---- .../msm/sde/rotator/sde_rotator_r3_internal.h | 5 +- 2 files changed, 168 insertions(+), 49 deletions(-) diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c index c141797bcd3c..c8d14a6d253b 100644 --- a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c +++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c @@ -77,6 +77,142 @@ #define SDE_ROTREG_READ(base, off) \ readl_relaxed(base + (off)) +/* Invalid software timestamp value for initialization */ +#define SDE_REGDMA_SWTS_INVALID (~0) + +/** + * sde_hw_rotator_elapsed_swts - Find difference of 2 software timestamps + * @ts_curr: current software timestamp + * @ts_prev: previous software timestamp + * @return: the amount ts_curr is ahead of ts_prev + */ +static int sde_hw_rotator_elapsed_swts(u32 ts_curr, u32 ts_prev) +{ + u32 diff = (ts_curr - ts_prev) & SDE_REGDMA_SWTS_MASK; + + return sign_extend32(diff, (SDE_REGDMA_SWTS_SHIFT - 1)); +} + +/** + * sde_hw_rotator_pending_swts - Check if the given context is still pending + * @rot: Pointer to hw rotator + * @ctx: Pointer to rotator context + * @pswts: Pointer to returned reference software timestamp, optional + * @return: true if context has pending requests + */ +static int sde_hw_rotator_pending_swts(struct sde_hw_rotator *rot, + struct sde_hw_rotator_context *ctx, u32 *pswts) +{ + u32 swts; + int ts_diff; + bool pending; + + if (ctx->last_regdma_timestamp == SDE_REGDMA_SWTS_INVALID) + swts = SDE_ROTREG_READ(rot->mdss_base, REGDMA_TIMESTAMP_REG); + else + swts = ctx->last_regdma_timestamp; + + if (ctx->q_id == ROT_QUEUE_LOW_PRIORITY) + swts >>= SDE_REGDMA_SWTS_SHIFT; + + swts &= SDE_REGDMA_SWTS_MASK; + + ts_diff = sde_hw_rotator_elapsed_swts(ctx->timestamp, swts); + + if (pswts) + *pswts = swts; + + pending = (ts_diff > 0) ? true : false; + + SDEROT_DBG("ts:0x%x, queue_id:%d, swts:0x%x, pending:%d\n", + ctx->timestamp, ctx->q_id, swts, pending); + return pending; +} + +/** + * sde_hw_rotator_enable_irq - Enable hw rotator interrupt with ref. count + * Also, clear rotator/regdma irq status. + * @rot: Pointer to hw rotator + */ +static void sde_hw_rotator_enable_irq(struct sde_hw_rotator *rot) +{ + SDEROT_DBG("irq_num:%d enabled:%d\n", rot->irq_num, + atomic_read(&rot->irq_enabled)); + + if (!atomic_read(&rot->irq_enabled)) { + if (rot->mode == ROT_REGDMA_OFF) + SDE_ROTREG_WRITE(rot->mdss_base, ROTTOP_INTR_CLEAR, + ROT_DONE_MASK); + else + SDE_ROTREG_WRITE(rot->mdss_base, + REGDMA_CSR_REGDMA_INT_CLEAR, REGDMA_INT_MASK); + + enable_irq(rot->irq_num); + } + atomic_inc(&rot->irq_enabled); +} + +/** + * sde_hw_rotator_disable_irq - Disable hw rotator interrupt with ref. count + * Also, clear rotator/regdma irq enable masks. + * @rot: Pointer to hw rotator + */ +static void sde_hw_rotator_disable_irq(struct sde_hw_rotator *rot) +{ + SDEROT_DBG("irq_num:%d enabled:%d\n", rot->irq_num, + atomic_read(&rot->irq_enabled)); + + if (!atomic_read(&rot->irq_enabled)) { + SDEROT_ERR("irq %d is already disabled\n", rot->irq_num); + return; + } + + if (!atomic_dec_return(&rot->irq_enabled)) { + if (rot->mode == ROT_REGDMA_OFF) + SDE_ROTREG_WRITE(rot->mdss_base, ROTTOP_INTR_EN, 0); + else + SDE_ROTREG_WRITE(rot->mdss_base, + REGDMA_CSR_REGDMA_INT_EN, 0); + /* disable irq after last pending irq is handled, if any */ + synchronize_irq(rot->irq_num); + disable_irq_nosync(rot->irq_num); + } +} + +/** + * sde_hw_rotator_dump_status - Dump hw rotator status on error + * @rot: Pointer to hw rotator + */ +static void sde_hw_rotator_dump_status(struct sde_hw_rotator *rot) +{ + SDEROT_ERR( + "op_mode = %x, int_en = %x, int_status = %x\n", + SDE_ROTREG_READ(rot->mdss_base, + REGDMA_CSR_REGDMA_OP_MODE), + SDE_ROTREG_READ(rot->mdss_base, + REGDMA_CSR_REGDMA_INT_EN), + SDE_ROTREG_READ(rot->mdss_base, + REGDMA_CSR_REGDMA_INT_STATUS)); + + SDEROT_ERR( + "ts = %x, q0_status = %x, q1_status = %x, block_status = %x\n", + SDE_ROTREG_READ(rot->mdss_base, + REGDMA_TIMESTAMP_REG), + SDE_ROTREG_READ(rot->mdss_base, + REGDMA_CSR_REGDMA_QUEUE_0_STATUS), + SDE_ROTREG_READ(rot->mdss_base, + REGDMA_CSR_REGDMA_QUEUE_1_STATUS), + SDE_ROTREG_READ(rot->mdss_base, + REGDMA_CSR_REGDMA_BLOCK_STATUS)); + + SDEROT_ERR( + "invalid_cmd_offset = %x, fsm_state = %x\n", + SDE_ROTREG_READ(rot->mdss_base, + REGDMA_CSR_REGDMA_INVALID_CMD_RAM_OFFSET), + SDE_ROTREG_READ(rot->mdss_base, + REGDMA_CSR_REGDMA_FSM_STATE)); +} + /** * sde_hw_rotator_get_ctx(): Retrieve rotator context from rotator HW based * on provided session_id. Each rotator has a different session_id. @@ -476,7 +612,7 @@ static u32 sde_hw_rotator_start_no_regdma(struct sde_hw_rotator_context *ctx, SDE_REGDMA_WRITE(wrptr, ROTTOP_INTR_EN, 1); SDE_REGDMA_WRITE(wrptr, ROTTOP_INTR_CLEAR, 1); reinit_completion(&ctx->rot_comp); - enable_irq(rot->irq_num); + sde_hw_rotator_enable_irq(rot); } SDE_REGDMA_WRITE(wrptr, ROTTOP_START_CTRL, 1); @@ -572,9 +708,6 @@ static u32 sde_hw_rotator_start_regdma(struct sde_hw_rotator_context *ctx, wrptr = sde_hw_rotator_get_regdma_segment(ctx); - if (rot->irq_num >= 0) - reinit_completion(&ctx->regdma_comp); - /* * Last ROT command must be ROT_START before REGDMA start */ @@ -676,7 +809,7 @@ static u32 sde_hw_rotator_wait_done_no_regdma( SDEROT_WARN( "Timeout waiting, but rotator job is done!!\n"); - disable_irq_nosync(rot->irq_num); + sde_hw_rotator_disable_irq(rot); } spin_unlock_irqrestore(&rot->rotisr_lock, flags); } else { @@ -719,13 +852,15 @@ static u32 sde_hw_rotator_wait_done_regdma( u32 last_isr; u32 last_ts; u32 int_id; + u32 swts; u32 sts = 0; unsigned long flags; if (rot->irq_num >= 0) { SDEROT_DBG("Wait for REGDMA completion, ctx:%p, ts:%X\n", ctx, ctx->timestamp); - rc = wait_for_completion_timeout(&ctx->regdma_comp, + rc = wait_event_timeout(ctx->regdma_waitq, + !sde_hw_rotator_pending_swts(rot, ctx, &swts), KOFF_TIMEOUT); spin_lock_irqsave(&rot->rotisr_lock, flags); @@ -738,11 +873,12 @@ static u32 sde_hw_rotator_wait_done_regdma( status, int_id, last_ts); if (rc == 0 || (status & REGDMA_INT_ERR_MASK)) { + bool pending; + + pending = sde_hw_rotator_pending_swts(rot, ctx, &swts); SDEROT_ERR( - "Timeout wait for regdma interrupt status, ts:%X\n", - ctx->timestamp); - SDEROT_ERR("last_isr:0x%X, last_ts:0x%X, rc=%d\n", - last_isr, last_ts, rc); + "Timeout wait for regdma interrupt status, ts:0x%X/0x%X pending:%d\n", + ctx->timestamp, swts, pending); if (status & REGDMA_WATCHDOG_INT) SDEROT_ERR("REGDMA watchdog interrupt\n"); @@ -753,24 +889,13 @@ static u32 sde_hw_rotator_wait_done_regdma( else if (status & REGDMA_INVALID_CMD) SDEROT_ERR("REGDMA invalid command\n"); + sde_hw_rotator_dump_status(rot); status = ROT_ERROR_BIT; - } else if (queue_id == ROT_QUEUE_HIGH_PRIORITY) { - /* Got to match exactly with interrupt ID */ - int_id = REGDMA_QUEUE0_INT0 << int_id; - - SDE_ROTREG_WRITE(rot->mdss_base, - REGDMA_CSR_REGDMA_INT_CLEAR, - int_id); - - status = 0; - } else if (queue_id == ROT_QUEUE_LOW_PRIORITY) { - /* Matching interrupt ID */ - int_id = REGDMA_QUEUE1_INT0 << int_id; - - SDE_ROTREG_WRITE(rot->mdss_base, - REGDMA_CSR_REGDMA_INT_CLEAR, - int_id); - + } else { + if (rc == 1) + SDEROT_WARN( + "REGDMA done but no irq, ts:0x%X/0x%X\n", + ctx->timestamp, swts); status = 0; } @@ -1007,7 +1132,7 @@ static struct sde_rot_hw_resource *sde_hw_rotator_alloc_ext( } if (resinfo->rot->irq_num >= 0) - enable_irq(resinfo->rot->irq_num); + sde_hw_rotator_enable_irq(resinfo->rot); SDEROT_DBG("New rotator resource:%p, priority:%d\n", resinfo, wb_id); @@ -1036,7 +1161,7 @@ static void sde_hw_rotator_free_ext(struct sde_rot_mgr *mgr, hw->pending_count); if (resinfo->rot->irq_num >= 0) - disable_irq(resinfo->rot->irq_num); + sde_hw_rotator_disable_irq(resinfo->rot); devm_kfree(&mgr->pdev->dev, resinfo); } @@ -1078,8 +1203,10 @@ static struct sde_hw_rotator_context *sde_hw_rotator_alloc_rotctx( ctx->q_id * SDE_HW_ROT_REGDMA_TOTAL_CTX + sde_hw_rotator_get_regdma_ctxidx(ctx)); + ctx->last_regdma_timestamp = SDE_REGDMA_SWTS_INVALID; + init_completion(&ctx->rot_comp); - init_completion(&ctx->regdma_comp); + init_waitqueue_head(&ctx->regdma_waitq); /* Store rotator context for lookup purpose */ sde_hw_rotator_put_ctx(ctx); @@ -1419,7 +1546,7 @@ static irqreturn_t sde_hw_rotator_rotirq_handler(int irq, void *ptr) if (isr & ROT_DONE_MASK) { if (rot->irq_num >= 0) - disable_irq_nosync(rot->irq_num); + sde_hw_rotator_disable_irq(rot); SDEROT_DBG("Notify rotator complete\n"); /* Normal rotator only 1 session, no need to lookup */ @@ -1456,6 +1583,8 @@ static irqreturn_t sde_hw_rotator_regdmairq_handler(int irq, void *ptr) u32 q_id; isr = SDE_ROTREG_READ(rot->mdss_base, REGDMA_CSR_REGDMA_INT_STATUS); + /* acknowledge interrupt before reading latest timestamp */ + SDE_ROTREG_WRITE(rot->mdss_base, REGDMA_CSR_REGDMA_INT_CLEAR, isr); ts = SDE_ROTREG_READ(rot->mdss_base, REGDMA_TIMESTAMP_REG); SDEROT_DBG("intr_status = %8.8x, sw_TS:%X\n", isr, ts); @@ -1480,30 +1609,23 @@ static irqreturn_t sde_hw_rotator_regdmairq_handler(int irq, void *ptr) } ctx = rot->rotCtx[q_id][ts & SDE_HW_ROT_REGDMA_SEG_MASK]; - WARN_ON(ctx == NULL); /* * Wake up all waiting context from the current and previous * SW Timestamp. */ - do { + while (ctx && + sde_hw_rotator_elapsed_swts(ctx->timestamp, ts) >= 0) { ctx->last_regdma_isr_status = isr; ctx->last_regdma_timestamp = ts; SDEROT_DBG( "regdma complete: ctx:%p, ts:%X\n", ctx, ts); - complete_all(&ctx->regdma_comp); + wake_up_all(&ctx->regdma_waitq); ts = (ts - 1) & SDE_REGDMA_SWTS_MASK; ctx = rot->rotCtx[q_id] [ts & SDE_HW_ROT_REGDMA_SEG_MASK]; - } while (ctx && (ctx->last_regdma_timestamp == 0)); - - /* - * Clear corresponding regdma interrupt because it is a level - * interrupt - */ - SDE_ROTREG_WRITE(rot->mdss_base, REGDMA_CSR_REGDMA_INT_CLEAR, - isr); + }; spin_unlock(&rot->rotisr_lock); ret = IRQ_HANDLED; @@ -1526,16 +1648,13 @@ static irqreturn_t sde_hw_rotator_regdmairq_handler(int irq, void *ptr) if (ctx && ctx->last_regdma_isr_status == 0) { ctx->last_regdma_isr_status = isr; ctx->last_regdma_timestamp = ts; - complete_all(&ctx->regdma_comp); + wake_up_all(&ctx->regdma_waitq); SDEROT_DBG("Wakeup rotctx[%d][%d]:%p\n", i, j, ctx); } } } - SDE_ROTREG_WRITE(rot->mdss_base, REGDMA_CSR_REGDMA_INT_CLEAR, - isr); - spin_unlock(&rot->rotisr_lock); ret = IRQ_HANDLED; } @@ -1810,6 +1929,7 @@ int sde_rotator_r3_init(struct sde_rot_mgr *mgr) disable_irq(rot->irq_num); } } + atomic_set(&rot->irq_enabled, 0); setup_rotator_ops(&rot->ops, rot->mode); diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_internal.h b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_internal.h index 610caf16c764..272b15e01e8b 100644 --- a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_internal.h +++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_internal.h @@ -202,7 +202,7 @@ struct sde_hw_rotator_context { u32 *regdma_wrptr; u32 timestamp; struct completion rot_comp; - struct completion regdma_comp; + wait_queue_head_t regdma_waitq; struct sde_dbg_buf src_dbgbuf; struct sde_dbg_buf dst_dbgbuf; u32 last_regdma_isr_status; @@ -253,6 +253,7 @@ struct sde_hw_rotator { /* logical interrupt number */ int irq_num; + atomic_t irq_enabled; /* internal ION memory for SW timestamp */ struct ion_client *iclient; @@ -260,8 +261,6 @@ struct sde_hw_rotator { void *swts_buffer; u32 highest_bank; - struct completion rot_comp; - struct completion regdma_comp; spinlock_t rotctx_lock; spinlock_t rotisr_lock;