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;