Only the FIMC entity memory-to-memory operation is fully covered, for the camera capture suspend/resume only stubs are provided. It's all what is needed to enable the driver on EXYNOS4 series with power domain driver enabled. Camera capture pipeline suspend/resume is covered by a separate patch. Signed-off-by: Sylwester Nawrocki <s.nawrocki@xxxxxxxxxxx> Signed-off-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx> --- drivers/media/video/s5p-fimc/fimc-capture.c | 18 ++ drivers/media/video/s5p-fimc/fimc-core.c | 273 ++++++++++++++++++++------- drivers/media/video/s5p-fimc/fimc-core.h | 16 +- drivers/media/video/s5p-fimc/fimc-reg.c | 2 +- 4 files changed, 231 insertions(+), 78 deletions(-) diff --git a/drivers/media/video/s5p-fimc/fimc-capture.c b/drivers/media/video/s5p-fimc/fimc-capture.c index 0d730e5..ea74e4b 100644 --- a/drivers/media/video/s5p-fimc/fimc-capture.c +++ b/drivers/media/video/s5p-fimc/fimc-capture.c @@ -17,6 +17,7 @@ #include <linux/interrupt.h> #include <linux/device.h> #include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <linux/list.h> #include <linux/slab.h> #include <linux/clk.h> @@ -196,6 +197,16 @@ static int fimc_stop_capture(struct fimc_dev *fimc) return 0; } +int fimc_capture_suspend(struct fimc_dev *fimc) +{ + return -EBUSY; +} + +int fimc_capture_resume(struct fimc_dev *fimc) +{ + return 0; +} + static int start_streaming(struct vb2_queue *q) { struct fimc_ctx *ctx = q->drv_priv; @@ -381,9 +392,14 @@ static int fimc_capture_open(struct file *file) if (fimc_m2m_active(fimc)) return -EBUSY; + ret = pm_runtime_get_sync(&fimc->pdev->dev); + if (ret) + return ret; + if (++fimc->vid_cap.refcnt == 1) { ret = fimc_isp_subdev_init(fimc, 0); if (ret) { + pm_runtime_put_sync(&fimc->pdev->dev); fimc->vid_cap.refcnt--; return -EIO; } @@ -411,6 +427,8 @@ static int fimc_capture_close(struct file *file) fimc_subdev_unregister(fimc); } + pm_runtime_put_sync(&fimc->pdev->dev); + return 0; } diff --git a/drivers/media/video/s5p-fimc/fimc-core.c b/drivers/media/video/s5p-fimc/fimc-core.c index aa55066..12e2d19 100644 --- a/drivers/media/video/s5p-fimc/fimc-core.c +++ b/drivers/media/video/s5p-fimc/fimc-core.c @@ -18,6 +18,7 @@ #include <linux/interrupt.h> #include <linux/device.h> #include <linux/platform_device.h> +#include <linux/pm_runtime.h> #include <linux/list.h> #include <linux/io.h> #include <linux/slab.h> @@ -301,7 +302,6 @@ int fimc_set_scaler_info(struct fimc_ctx *ctx) static void fimc_m2m_job_finish(struct fimc_ctx *ctx, int vb_state) { struct vb2_buffer *src_vb, *dst_vb; - struct fimc_dev *fimc = ctx->fimc_dev; if (!ctx || !ctx->m2m_ctx) return; @@ -312,43 +312,52 @@ static void fimc_m2m_job_finish(struct fimc_ctx *ctx, int vb_state) if (src_vb && dst_vb) { v4l2_m2m_buf_done(src_vb, vb_state); v4l2_m2m_buf_done(dst_vb, vb_state); - v4l2_m2m_job_finish(fimc->m2m.m2m_dev, ctx->m2m_ctx); + v4l2_m2m_job_finish(ctx->fimc_dev->m2m.m2m_dev, + ctx->m2m_ctx); } } /* Complete the transaction which has been scheduled for execution. */ -static void fimc_m2m_shutdown(struct fimc_ctx *ctx) +static int fimc_m2m_shutdown(struct fimc_ctx *ctx) { struct fimc_dev *fimc = ctx->fimc_dev; int ret; if (!fimc_m2m_pending(fimc)) - return; + return 0; fimc_ctx_state_lock_set(FIMC_CTX_SHUT, ctx); ret = wait_event_timeout(fimc->irq_queue, !fimc_ctx_state_is_set(FIMC_CTX_SHUT, ctx), FIMC_SHUTDOWN_TIMEOUT); - /* - * In case of a timeout the buffers are not released in the interrupt - * handler so return them here with the error flag set, if there are - * any on the queue. - */ - if (ret == 0) - fimc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR); + + return ret == 0 ? -ETIMEDOUT : ret; +} + +static int start_streaming(struct vb2_queue *q) +{ + struct fimc_ctx *ctx = q->drv_priv; + int ret; + + ret = pm_runtime_get_sync(&ctx->fimc_dev->pdev->dev); + return ret > 0 ? 0 : ret; } static int stop_streaming(struct vb2_queue *q) { struct fimc_ctx *ctx = q->drv_priv; + int ret; - fimc_m2m_shutdown(ctx); + ret = fimc_m2m_shutdown(ctx); + if (ret == -ETIMEDOUT) + fimc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR); + pm_runtime_put(&ctx->fimc_dev->pdev->dev); return 0; } -static void fimc_capture_irq_handler(struct fimc_dev *fimc) +void fimc_capture_irq_handler(struct fimc_dev *fimc) { struct fimc_vid_cap *cap = &fimc->vid_cap; struct fimc_vid_buffer *v_buf; @@ -403,7 +412,7 @@ static void fimc_capture_irq_handler(struct fimc_dev *fimc) fimc_hw_get_frame_index(fimc), cap->active_buf_cnt); } -static irqreturn_t fimc_isr(int irq, void *priv) +static irqreturn_t fimc_irq_handler(int irq, void *priv) { struct fimc_dev *fimc = priv; struct fimc_vid_cap *cap = &fimc->vid_cap; @@ -411,9 +420,17 @@ static irqreturn_t fimc_isr(int irq, void *priv) fimc_hw_clear_irq(fimc); + spin_lock(&fimc->slock); + if (test_and_clear_bit(ST_M2M_PEND, &fimc->state)) { + if (test_and_clear_bit(ST_M2M_SUSPENDING, &fimc->state)) { + set_bit(ST_M2M_SUSPENDED, &fimc->state); + wake_up(&fimc->irq_queue); + goto out; + } ctx = v4l2_m2m_get_curr_priv(fimc->m2m.m2m_dev); if (ctx != NULL) { + spin_unlock(&fimc->slock); fimc_m2m_job_finish(ctx, VB2_BUF_STATE_DONE); spin_lock(&ctx->slock); @@ -423,21 +440,18 @@ static irqreturn_t fimc_isr(int irq, void *priv) } spin_unlock(&ctx->slock); } - return IRQ_HANDLED; - } - - spin_lock(&fimc->slock); - - if (test_bit(ST_CAPT_PEND, &fimc->state)) { - fimc_capture_irq_handler(fimc); + } else { + if (test_bit(ST_CAPT_PEND, &fimc->state)) { + fimc_capture_irq_handler(fimc); - if (cap->active_buf_cnt == 1) { - fimc_deactivate_capture(fimc); - clear_bit(ST_CAPT_STREAM, &fimc->state); + if (cap->active_buf_cnt == 1) { + fimc_deactivate_capture(fimc); + clear_bit(ST_CAPT_STREAM, &fimc->state); + } } } - +out: spin_unlock(&fimc->slock); return IRQ_HANDLED; } @@ -635,10 +649,10 @@ static void fimc_dma_run(void *priv) return; fimc = ctx->fimc_dev; - - spin_lock_irqsave(&ctx->slock, flags); + spin_lock_irqsave(&fimc->slock, flags); set_bit(ST_M2M_PEND, &fimc->state); + spin_lock(&ctx->slock); ctx->state |= (FIMC_SRC_ADDR | FIMC_DST_ADDR); ret = fimc_prepare_config(ctx, ctx->state); if (ret) @@ -649,8 +663,6 @@ static void fimc_dma_run(void *priv) ctx->state |= FIMC_PARAMS; fimc->m2m.ctx = ctx; } - - spin_lock(&fimc->slock); fimc_hw_set_input_addr(fimc, &ctx->s_frame.paddr); if (ctx->state & FIMC_PARAMS) { @@ -680,10 +692,9 @@ static void fimc_dma_run(void *priv) ctx->state &= (FIMC_CTX_M2M | FIMC_CTX_CAP | FIMC_SRC_FMT | FIMC_DST_FMT); fimc_hw_activate_input_dma(fimc, true); - spin_unlock(&fimc->slock); - dma_unlock: - spin_unlock_irqrestore(&ctx->slock, flags); + spin_unlock(&ctx->slock); + spin_unlock_irqrestore(&fimc->slock, flags); } static void fimc_job_abort(void *priv) @@ -762,6 +773,7 @@ static struct vb2_ops fimc_qops = { .wait_prepare = fimc_unlock, .wait_finish = fimc_lock, .stop_streaming = stop_streaming, + .start_streaming = start_streaming, }; static int fimc_m2m_querycap(struct file *file, void *priv, @@ -873,7 +885,6 @@ int fimc_vidioc_try_fmt_mplane(struct file *file, void *priv, u32 max_width, mod_x, mod_y, mask; int i, is_output = 0; - if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { if (fimc_ctx_state_is_set(FIMC_CTX_CAP, ctx)) return -EINVAL; @@ -1408,9 +1419,6 @@ static int fimc_m2m_open(struct file *file) if (fimc->vid_cap.refcnt > 0) return -EBUSY; - fimc->m2m.refcnt++; - set_bit(ST_OUTDMA_RUN, &fimc->state); - ctx = kzalloc(sizeof *ctx, GFP_KERNEL); if (!ctx) return -ENOMEM; @@ -1434,6 +1442,9 @@ static int fimc_m2m_open(struct file *file) return err; } + if (fimc->m2m.refcnt++ == 0) + set_bit(ST_M2M_RUN, &fimc->state); + return 0; } @@ -1446,10 +1457,10 @@ static int fimc_m2m_release(struct file *file) task_pid_nr(current), fimc->state, fimc->m2m.refcnt); v4l2_m2m_ctx_release(ctx->m2m_ctx); - kfree(ctx); - if (--fimc->m2m.refcnt <= 0) - clear_bit(ST_OUTDMA_RUN, &fimc->state); + if (--fimc->m2m.refcnt <= 0) + clear_bit(ST_M2M_RUN, &fimc->state); + kfree(ctx); return 0; } @@ -1551,7 +1562,7 @@ err_m2m_r1: return ret; } -static void fimc_unregister_m2m_device(struct fimc_dev *fimc) +void fimc_unregister_m2m_device(struct fimc_dev *fimc) { if (fimc) { v4l2_m2m_release(fimc->m2m.m2m_dev); @@ -1561,14 +1572,12 @@ static void fimc_unregister_m2m_device(struct fimc_dev *fimc) } } -static void fimc_clk_release(struct fimc_dev *fimc) +static void fimc_clk_put(struct fimc_dev *fimc) { int i; for (i = 0; i < fimc->num_clocks; i++) { - if (fimc->clock[i]) { - clk_disable(fimc->clock[i]); + if (fimc->clock[i]) clk_put(fimc->clock[i]); - } } } @@ -1577,15 +1586,50 @@ static int fimc_clk_get(struct fimc_dev *fimc) int i; for (i = 0; i < fimc->num_clocks; i++) { fimc->clock[i] = clk_get(&fimc->pdev->dev, fimc_clocks[i]); - - if (!IS_ERR_OR_NULL(fimc->clock[i])) { - clk_enable(fimc->clock[i]); + if (!IS_ERR_OR_NULL(fimc->clock[i])) continue; - } dev_err(&fimc->pdev->dev, "failed to get fimc clock: %s\n", fimc_clocks[i]); return -ENXIO; } + + return 0; +} + +static int fimc_m2m_suspend(struct fimc_dev *fimc) +{ + unsigned long flags; + int timeout; + + spin_lock_irqsave(&fimc->slock, flags); + if (!fimc_m2m_pending(fimc)) { + spin_unlock_irqrestore(&fimc->slock, flags); + return 0; + } + clear_bit(ST_M2M_SUSPENDED, &fimc->state); + set_bit(ST_M2M_SUSPENDING, &fimc->state); + spin_unlock_irqrestore(&fimc->slock, flags); + + timeout = wait_event_timeout(fimc->irq_queue, + test_bit(ST_M2M_SUSPENDED, &fimc->state), + FIMC_SHUTDOWN_TIMEOUT); + + clear_bit(ST_M2M_SUSPENDING, &fimc->state); + return timeout == 0 ? -EAGAIN: 0; +} + +static int fimc_m2m_resume(struct fimc_dev *fimc) +{ + unsigned long flags; + + /* Need to fully setup hardware at first run. */ + spin_lock_irqsave(&fimc->slock, flags); + fimc->m2m.ctx = NULL; + spin_unlock_irqrestore(&fimc->slock, flags); + + if (test_and_clear_bit(ST_M2M_SUSPENDED, &fimc->state)) + fimc_m2m_job_finish(fimc->m2m.ctx, + VB2_BUF_STATE_ERROR); return 0; } @@ -1614,11 +1658,13 @@ static int fimc_probe(struct platform_device *pdev) return -ENOMEM; fimc->id = pdev->id; + fimc->variant = drv_data->variant[fimc->id]; fimc->pdev = pdev; pdata = pdev->dev.platform_data; fimc->pdata = pdata; - fimc->state = ST_IDLE; + + set_bit(ST_LPM, &fimc->state); init_waitqueue_head(&fimc->irq_queue); spin_lock_init(&fimc->slock); @@ -1655,27 +1701,32 @@ static int fimc_probe(struct platform_device *pdev) fimc->num_clocks++; } - ret = fimc_clk_get(fimc); - if (ret) - goto err_regs_unmap; - clk_set_rate(fimc->clock[CLK_BUS], drv_data->lclk_frequency); - res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res) { dev_err(&pdev->dev, "failed to get IRQ resource\n"); ret = -ENXIO; - goto err_clk; + goto err_regs_unmap; } fimc->irq = res->start; - fimc_hw_reset(fimc); + ret = fimc_clk_get(fimc); + if (ret) + goto err_regs_unmap; + clk_set_rate(fimc->clock[CLK_BUS], drv_data->lclk_frequency); + clk_enable(fimc->clock[CLK_BUS]); + + platform_set_drvdata(pdev, fimc); - ret = request_irq(fimc->irq, fimc_isr, 0, pdev->name, fimc); + ret = request_irq(fimc->irq, fimc_irq_handler, 0, pdev->name, fimc); if (ret) { dev_err(&pdev->dev, "failed to install irq (%d)\n", ret); goto err_clk; } + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_get_sync(&fimc->pdev->dev); + if (ret < 0) + goto err_clk; /* Initialize contiguous memory allocator */ fimc->alloc_ctx = vb2_dma_contig_init_ctx(&fimc->pdev->dev); if (IS_ERR(fimc->alloc_ctx)) { @@ -1692,18 +1743,12 @@ static int fimc_probe(struct platform_device *pdev) ret = fimc_register_capture_device(fimc); if (ret) goto err_m2m; - clk_disable(fimc->clock[CLK_CAM]); } - /* - * Exclude the additional output DMA address registers by masking - * them out on HW revisions that provide extended capabilites. - */ - if (fimc->variant->out_buf_count > 4) - fimc_hw_set_dma_seq(fimc, 0xF); dev_dbg(&pdev->dev, "%s(): fimc-%d registered successfully\n", __func__, fimc->id); + pm_runtime_put(&fimc->pdev->dev); return 0; err_m2m: @@ -1711,7 +1756,7 @@ err_m2m: err_irq: free_irq(fimc->irq, fimc); err_clk: - fimc_clk_release(fimc); + fimc_clk_put(fimc); err_regs_unmap: iounmap(fimc->regs); err_req_region: @@ -1723,27 +1768,105 @@ err_info: return ret; } -static int __devexit fimc_remove(struct platform_device *pdev) +static int fimc_runtime_resume(struct device *dev) { - struct fimc_dev *fimc = - (struct fimc_dev *)platform_get_drvdata(pdev); + struct fimc_dev *fimc = dev_get_drvdata(dev); - free_irq(fimc->irq, fimc); + dbg("fimc%d: state: 0x%lx", fimc->id, fimc->state); + + /* Enable clocks and perform basic initalization */ + clk_enable(fimc->clock[CLK_GATE]); + fimc_hw_reset(fimc); + if (fimc->variant->out_buf_count > 4) + fimc_hw_set_dma_seq(fimc, 0xF); + + /* Resume the capture or mem-to-mem device */ + if (fimc_capture_in_use(fimc)) + return fimc_capture_resume(fimc); + else if (fimc_m2m_pending(fimc)) + return fimc_m2m_resume(fimc); + return 0; +} + +static int fimc_runtime_suspend(struct device *dev) +{ + struct fimc_dev *fimc = dev_get_drvdata(dev); + int ret = 0; + + if (fimc_capture_in_use(fimc)) + ret = fimc_capture_suspend(fimc); + else + ret = fimc_m2m_suspend(fimc); + if (!ret) + clk_disable(fimc->clock[CLK_GATE]); + + dbg("fimc%d: state: 0x%lx", fimc->id, fimc->state); + return ret; +} + +#ifdef CONFIG_PM_SLEEP +static int fimc_resume(struct device *dev) +{ + struct fimc_dev *fimc = dev_get_drvdata(dev); + unsigned long flags; + + dbg("fimc%d: state: 0x%lx", fimc->id, fimc->state); + + /* Do not resume if the device was idle before system suspend */ + spin_lock_irqsave(&fimc->slock, flags); + if (!test_and_clear_bit(ST_LPM, &fimc->state) || + (!fimc_m2m_active(fimc) && !fimc_capture_in_use(fimc))) { + spin_unlock_irqrestore(&fimc->slock, flags); + return 0; + } fimc_hw_reset(fimc); + if (fimc->variant->out_buf_count > 4) + fimc_hw_set_dma_seq(fimc, 0xF); + spin_unlock_irqrestore(&fimc->slock, flags); + + if (fimc_capture_in_use(fimc)) + return fimc_capture_resume(fimc); + + return fimc_m2m_resume(fimc); +} + +static int fimc_suspend(struct device *dev) +{ + struct fimc_dev *fimc = dev_get_drvdata(dev); + + dbg("fimc%d: state: 0x%lx", fimc->id, fimc->state); + + if (test_and_set_bit(ST_LPM, &fimc->state)) + return 0; + if (fimc_capture_in_use(fimc)) + return fimc_capture_suspend(fimc); + + return fimc_m2m_suspend(fimc); +} +#endif /* CONFIG_PM_SLEEP */ + +static int __devexit fimc_remove(struct platform_device *pdev) +{ + struct fimc_dev *fimc = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + fimc_runtime_suspend(&pdev->dev); + pm_runtime_set_suspended(&pdev->dev); fimc_unregister_m2m_device(fimc); fimc_unregister_capture_device(fimc); - fimc_clk_release(fimc); - vb2_dma_contig_cleanup_ctx(fimc->alloc_ctx); + clk_disable(fimc->clock[CLK_BUS]); + fimc_clk_put(fimc); + free_irq(fimc->irq, fimc); iounmap(fimc->regs); release_resource(fimc->regs_res); kfree(fimc->regs_res); kfree(fimc); - dev_info(&pdev->dev, "%s driver unloaded\n", pdev->name); + dev_info(&pdev->dev, "driver unloaded\n"); return 0; } @@ -1906,6 +2029,11 @@ static struct platform_device_id fimc_driver_ids[] = { }; MODULE_DEVICE_TABLE(platform, fimc_driver_ids); +static const struct dev_pm_ops fimc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(fimc_suspend, fimc_resume) + SET_RUNTIME_PM_OPS(fimc_runtime_suspend, fimc_runtime_resume, NULL) +}; + static struct platform_driver fimc_driver = { .probe = fimc_probe, .remove = __devexit_p(fimc_remove), @@ -1913,6 +2041,7 @@ static struct platform_driver fimc_driver = { .driver = { .name = MODULE_NAME, .owner = THIS_MODULE, + .pm = &fimc_pm_ops, } }; diff --git a/drivers/media/video/s5p-fimc/fimc-core.h b/drivers/media/video/s5p-fimc/fimc-core.h index 1f70772..21dfcac 100644 --- a/drivers/media/video/s5p-fimc/fimc-core.h +++ b/drivers/media/video/s5p-fimc/fimc-core.h @@ -48,22 +48,26 @@ enum { }; enum fimc_dev_flags { - /* for m2m node */ - ST_IDLE, - ST_OUTDMA_RUN, + ST_LPM, + /* m2m node */ + ST_M2M_RUN, ST_M2M_PEND, - /* for capture node */ + ST_M2M_SUSPENDING, + ST_M2M_SUSPENDED, + /* capture node */ ST_CAPT_PEND, ST_CAPT_RUN, ST_CAPT_STREAM, ST_CAPT_SHUT, + ST_CAPT_INUSE, }; -#define fimc_m2m_active(dev) test_bit(ST_OUTDMA_RUN, &(dev)->state) +#define fimc_m2m_active(dev) test_bit(ST_M2M_RUN, &(dev)->state) #define fimc_m2m_pending(dev) test_bit(ST_M2M_PEND, &(dev)->state) #define fimc_capture_running(dev) test_bit(ST_CAPT_RUN, &(dev)->state) #define fimc_capture_pending(dev) test_bit(ST_CAPT_PEND, &(dev)->state) +#define fimc_capture_in_use(dev) test_bit(ST_CAPT_INUSE, &(dev)->state) enum fimc_datapath { FIMC_CAMERA, @@ -644,6 +648,8 @@ void fimc_unregister_capture_device(struct fimc_dev *fimc); int fimc_sensor_sd_init(struct fimc_dev *fimc, int index); int fimc_vid_cap_buf_queue(struct fimc_dev *fimc, struct fimc_vid_buffer *fimc_vb); +int fimc_capture_suspend(struct fimc_dev *fimc); +int fimc_capture_resume(struct fimc_dev *fimc); /* Locking: the caller holds fimc->slock */ static inline void fimc_activate_capture(struct fimc_ctx *ctx) diff --git a/drivers/media/video/s5p-fimc/fimc-reg.c b/drivers/media/video/s5p-fimc/fimc-reg.c index 4893b2d..938dadf 100644 --- a/drivers/media/video/s5p-fimc/fimc-reg.c +++ b/drivers/media/video/s5p-fimc/fimc-reg.c @@ -30,7 +30,7 @@ void fimc_hw_reset(struct fimc_dev *dev) cfg = readl(dev->regs + S5P_CIGCTRL); cfg |= (S5P_CIGCTRL_SWRST | S5P_CIGCTRL_IRQ_LEVEL); writel(cfg, dev->regs + S5P_CIGCTRL); - udelay(1000); + udelay(10); cfg = readl(dev->regs + S5P_CIGCTRL); cfg &= ~S5P_CIGCTRL_SWRST; -- 1.7.5.4 -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html