]> git.proxmox.com Git - mirror_ubuntu-disco-kernel.git/commitdiff
Merge tag 'v3.9-rc5' into patchwork
authorMauro Carvalho Chehab <mchehab@redhat.com>
Mon, 1 Apr 2013 12:54:14 +0000 (09:54 -0300)
committerMauro Carvalho Chehab <mchehab@redhat.com>
Mon, 1 Apr 2013 12:54:14 +0000 (09:54 -0300)
Linux 3.9-rc5

* tag 'v3.9-rc5': (1080 commits)
  Linux 3.9-rc5
  Revert "lockdep: check that no locks held at freeze time"
  dw_dmac: adjust slave_id accordingly to request line base
  dmaengine: dw_dma: fix endianess for DT xlate function
  PNP: List Rafael Wysocki as a maintainer
  rbd: don't zero-fill non-image object requests
  ia64 idle: delete stale (*idle)() function pointer
  Btrfs: don't drop path when printing out tree errors in scrub
  target: Fix RESERVATION_CONFLICT status regression for iscsi-target special case
  tcm_vhost: Avoid VIRTIO_RING_F_EVENT_IDX feature bit
  Revert "mm: introduce VM_POPULATE flag to better deal with racy userspace programs"
  usb: ftdi_sio: Add support for Mitsubishi FX-USB-AW/-BD
  mg_disk: fix error return code in mg_probe()
  Btrfs: fix wrong return value of btrfs_lookup_csum()
  Btrfs: fix wrong reservation of csums
  Btrfs: fix double free in the btrfs_qgroup_account_ref()
  Btrfs: limit the global reserve to 512mb
  Btrfs: hold the ordered operations mutex when waiting on ordered extents
  Btrfs: fix space accounting for unlink and rename
  Btrfs: fix space leak when we fail to reserve metadata space
  ...

1  2 
MAINTAINERS
drivers/media/pci/bt8xx/bttv-driver.c
drivers/media/platform/exynos4-is/fimc-core.c
drivers/media/platform/exynos4-is/fimc-lite-reg.c
drivers/media/platform/exynos4-is/fimc-lite.c
drivers/media/platform/exynos4-is/media-dev.c
drivers/media/platform/s5p-mfc/s5p_mfc.c
drivers/media/v4l2-core/Makefile
drivers/mfd/Kconfig

diff --cc MAINTAINERS
Simple merge
Simple merge
index 1248cdd35c295a74cb05177556c6e9e6e683a8ab,0000000000000000000000000000000000000000..44239e5b3a47254600ed7827894459879fbe0806
mode 100644,000000..100644
--- /dev/null
@@@ -1,1334 -1,0 +1,1336 @@@
-               fimc_m2m_job_finish(fimc->m2m.ctx,
-                                   VB2_BUF_STATE_ERROR);
 +/*
 + * Samsung S5P/EXYNOS4 SoC series FIMC (CAMIF) driver
 + *
 + * Copyright (C) 2010-2012 Samsung Electronics Co., Ltd.
 + * Sylwester Nawrocki <s.nawrocki@samsung.com>
 + *
 + * This program is free software; you can redistribute it and/or modify
 + * it under the terms of the GNU General Public License as published
 + * by the Free Software Foundation, either version 2 of the License,
 + * or (at your option) any later version.
 + */
 +
 +#include <linux/module.h>
 +#include <linux/kernel.h>
 +#include <linux/types.h>
 +#include <linux/errno.h>
 +#include <linux/bug.h>
 +#include <linux/interrupt.h>
 +#include <linux/device.h>
 +#include <linux/platform_device.h>
 +#include <linux/pm_runtime.h>
 +#include <linux/list.h>
 +#include <linux/mfd/syscon.h>
 +#include <linux/io.h>
 +#include <linux/of.h>
 +#include <linux/of_device.h>
 +#include <linux/slab.h>
 +#include <linux/clk.h>
 +#include <media/v4l2-ioctl.h>
 +#include <media/videobuf2-core.h>
 +#include <media/videobuf2-dma-contig.h>
 +
 +#include "fimc-core.h"
 +#include "fimc-reg.h"
 +#include "media-dev.h"
 +
 +static char *fimc_clocks[MAX_FIMC_CLOCKS] = {
 +      "sclk_fimc", "fimc"
 +};
 +
 +static struct fimc_fmt fimc_formats[] = {
 +      {
 +              .name           = "RGB565",
 +              .fourcc         = V4L2_PIX_FMT_RGB565,
 +              .depth          = { 16 },
 +              .color          = FIMC_FMT_RGB565,
 +              .memplanes      = 1,
 +              .colplanes      = 1,
 +              .flags          = FMT_FLAGS_M2M,
 +      }, {
 +              .name           = "BGR666",
 +              .fourcc         = V4L2_PIX_FMT_BGR666,
 +              .depth          = { 32 },
 +              .color          = FIMC_FMT_RGB666,
 +              .memplanes      = 1,
 +              .colplanes      = 1,
 +              .flags          = FMT_FLAGS_M2M,
 +      }, {
 +              .name           = "ARGB8888, 32 bpp",
 +              .fourcc         = V4L2_PIX_FMT_RGB32,
 +              .depth          = { 32 },
 +              .color          = FIMC_FMT_RGB888,
 +              .memplanes      = 1,
 +              .colplanes      = 1,
 +              .flags          = FMT_FLAGS_M2M | FMT_HAS_ALPHA,
 +      }, {
 +              .name           = "ARGB1555",
 +              .fourcc         = V4L2_PIX_FMT_RGB555,
 +              .depth          = { 16 },
 +              .color          = FIMC_FMT_RGB555,
 +              .memplanes      = 1,
 +              .colplanes      = 1,
 +              .flags          = FMT_FLAGS_M2M_OUT | FMT_HAS_ALPHA,
 +      }, {
 +              .name           = "ARGB4444",
 +              .fourcc         = V4L2_PIX_FMT_RGB444,
 +              .depth          = { 16 },
 +              .color          = FIMC_FMT_RGB444,
 +              .memplanes      = 1,
 +              .colplanes      = 1,
 +              .flags          = FMT_FLAGS_M2M_OUT | FMT_HAS_ALPHA,
 +      }, {
 +              .name           = "YUV 4:4:4",
 +              .mbus_code      = V4L2_MBUS_FMT_YUV10_1X30,
 +              .flags          = FMT_FLAGS_WRITEBACK,
 +      }, {
 +              .name           = "YUV 4:2:2 packed, YCbYCr",
 +              .fourcc         = V4L2_PIX_FMT_YUYV,
 +              .depth          = { 16 },
 +              .color          = FIMC_FMT_YCBYCR422,
 +              .memplanes      = 1,
 +              .colplanes      = 1,
 +              .mbus_code      = V4L2_MBUS_FMT_YUYV8_2X8,
 +              .flags          = FMT_FLAGS_M2M | FMT_FLAGS_CAM,
 +      }, {
 +              .name           = "YUV 4:2:2 packed, CbYCrY",
 +              .fourcc         = V4L2_PIX_FMT_UYVY,
 +              .depth          = { 16 },
 +              .color          = FIMC_FMT_CBYCRY422,
 +              .memplanes      = 1,
 +              .colplanes      = 1,
 +              .mbus_code      = V4L2_MBUS_FMT_UYVY8_2X8,
 +              .flags          = FMT_FLAGS_M2M | FMT_FLAGS_CAM,
 +      }, {
 +              .name           = "YUV 4:2:2 packed, CrYCbY",
 +              .fourcc         = V4L2_PIX_FMT_VYUY,
 +              .depth          = { 16 },
 +              .color          = FIMC_FMT_CRYCBY422,
 +              .memplanes      = 1,
 +              .colplanes      = 1,
 +              .mbus_code      = V4L2_MBUS_FMT_VYUY8_2X8,
 +              .flags          = FMT_FLAGS_M2M | FMT_FLAGS_CAM,
 +      }, {
 +              .name           = "YUV 4:2:2 packed, YCrYCb",
 +              .fourcc         = V4L2_PIX_FMT_YVYU,
 +              .depth          = { 16 },
 +              .color          = FIMC_FMT_YCRYCB422,
 +              .memplanes      = 1,
 +              .colplanes      = 1,
 +              .mbus_code      = V4L2_MBUS_FMT_YVYU8_2X8,
 +              .flags          = FMT_FLAGS_M2M | FMT_FLAGS_CAM,
 +      }, {
 +              .name           = "YUV 4:2:2 planar, Y/Cb/Cr",
 +              .fourcc         = V4L2_PIX_FMT_YUV422P,
 +              .depth          = { 12 },
 +              .color          = FIMC_FMT_YCBYCR422,
 +              .memplanes      = 1,
 +              .colplanes      = 3,
 +              .flags          = FMT_FLAGS_M2M,
 +      }, {
 +              .name           = "YUV 4:2:2 planar, Y/CbCr",
 +              .fourcc         = V4L2_PIX_FMT_NV16,
 +              .depth          = { 16 },
 +              .color          = FIMC_FMT_YCBYCR422,
 +              .memplanes      = 1,
 +              .colplanes      = 2,
 +              .flags          = FMT_FLAGS_M2M,
 +      }, {
 +              .name           = "YUV 4:2:2 planar, Y/CrCb",
 +              .fourcc         = V4L2_PIX_FMT_NV61,
 +              .depth          = { 16 },
 +              .color          = FIMC_FMT_YCRYCB422,
 +              .memplanes      = 1,
 +              .colplanes      = 2,
 +              .flags          = FMT_FLAGS_M2M,
 +      }, {
 +              .name           = "YUV 4:2:0 planar, YCbCr",
 +              .fourcc         = V4L2_PIX_FMT_YUV420,
 +              .depth          = { 12 },
 +              .color          = FIMC_FMT_YCBCR420,
 +              .memplanes      = 1,
 +              .colplanes      = 3,
 +              .flags          = FMT_FLAGS_M2M,
 +      }, {
 +              .name           = "YUV 4:2:0 planar, Y/CbCr",
 +              .fourcc         = V4L2_PIX_FMT_NV12,
 +              .depth          = { 12 },
 +              .color          = FIMC_FMT_YCBCR420,
 +              .memplanes      = 1,
 +              .colplanes      = 2,
 +              .flags          = FMT_FLAGS_M2M,
 +      }, {
 +              .name           = "YUV 4:2:0 non-contig. 2p, Y/CbCr",
 +              .fourcc         = V4L2_PIX_FMT_NV12M,
 +              .color          = FIMC_FMT_YCBCR420,
 +              .depth          = { 8, 4 },
 +              .memplanes      = 2,
 +              .colplanes      = 2,
 +              .flags          = FMT_FLAGS_M2M,
 +      }, {
 +              .name           = "YUV 4:2:0 non-contig. 3p, Y/Cb/Cr",
 +              .fourcc         = V4L2_PIX_FMT_YUV420M,
 +              .color          = FIMC_FMT_YCBCR420,
 +              .depth          = { 8, 2, 2 },
 +              .memplanes      = 3,
 +              .colplanes      = 3,
 +              .flags          = FMT_FLAGS_M2M,
 +      }, {
 +              .name           = "YUV 4:2:0 non-contig. 2p, tiled",
 +              .fourcc         = V4L2_PIX_FMT_NV12MT,
 +              .color          = FIMC_FMT_YCBCR420,
 +              .depth          = { 8, 4 },
 +              .memplanes      = 2,
 +              .colplanes      = 2,
 +              .flags          = FMT_FLAGS_M2M,
 +      }, {
 +              .name           = "JPEG encoded data",
 +              .fourcc         = V4L2_PIX_FMT_JPEG,
 +              .color          = FIMC_FMT_JPEG,
 +              .depth          = { 8 },
 +              .memplanes      = 1,
 +              .colplanes      = 1,
 +              .mbus_code      = V4L2_MBUS_FMT_JPEG_1X8,
 +              .flags          = FMT_FLAGS_CAM | FMT_FLAGS_COMPRESSED,
 +      }, {
 +              .name           = "S5C73MX interleaved UYVY/JPEG",
 +              .fourcc         = V4L2_PIX_FMT_S5C_UYVY_JPG,
 +              .color          = FIMC_FMT_YUYV_JPEG,
 +              .depth          = { 8 },
 +              .memplanes      = 2,
 +              .colplanes      = 1,
 +              .mdataplanes    = 0x2, /* plane 1 holds frame meta data */
 +              .mbus_code      = V4L2_MBUS_FMT_S5C_UYVY_JPEG_1X8,
 +              .flags          = FMT_FLAGS_CAM | FMT_FLAGS_COMPRESSED,
 +      },
 +};
 +
 +struct fimc_fmt *fimc_get_format(unsigned int index)
 +{
 +      if (index >= ARRAY_SIZE(fimc_formats))
 +              return NULL;
 +
 +      return &fimc_formats[index];
 +}
 +
 +int fimc_check_scaler_ratio(struct fimc_ctx *ctx, int sw, int sh,
 +                          int dw, int dh, int rotation)
 +{
 +      if (rotation == 90 || rotation == 270)
 +              swap(dw, dh);
 +
 +      if (!ctx->scaler.enabled)
 +              return (sw == dw && sh == dh) ? 0 : -EINVAL;
 +
 +      if ((sw >= SCALER_MAX_HRATIO * dw) || (sh >= SCALER_MAX_VRATIO * dh))
 +              return -EINVAL;
 +
 +      return 0;
 +}
 +
 +static int fimc_get_scaler_factor(u32 src, u32 tar, u32 *ratio, u32 *shift)
 +{
 +      u32 sh = 6;
 +
 +      if (src >= 64 * tar)
 +              return -EINVAL;
 +
 +      while (sh--) {
 +              u32 tmp = 1 << sh;
 +              if (src >= tar * tmp) {
 +                      *shift = sh, *ratio = tmp;
 +                      return 0;
 +              }
 +      }
 +      *shift = 0, *ratio = 1;
 +      return 0;
 +}
 +
 +int fimc_set_scaler_info(struct fimc_ctx *ctx)
 +{
 +      const struct fimc_variant *variant = ctx->fimc_dev->variant;
 +      struct device *dev = &ctx->fimc_dev->pdev->dev;
 +      struct fimc_scaler *sc = &ctx->scaler;
 +      struct fimc_frame *s_frame = &ctx->s_frame;
 +      struct fimc_frame *d_frame = &ctx->d_frame;
 +      int tx, ty, sx, sy;
 +      int ret;
 +
 +      if (ctx->rotation == 90 || ctx->rotation == 270) {
 +              ty = d_frame->width;
 +              tx = d_frame->height;
 +      } else {
 +              tx = d_frame->width;
 +              ty = d_frame->height;
 +      }
 +      if (tx <= 0 || ty <= 0) {
 +              dev_err(dev, "Invalid target size: %dx%d\n", tx, ty);
 +              return -EINVAL;
 +      }
 +
 +      sx = s_frame->width;
 +      sy = s_frame->height;
 +      if (sx <= 0 || sy <= 0) {
 +              dev_err(dev, "Invalid source size: %dx%d\n", sx, sy);
 +              return -EINVAL;
 +      }
 +      sc->real_width = sx;
 +      sc->real_height = sy;
 +
 +      ret = fimc_get_scaler_factor(sx, tx, &sc->pre_hratio, &sc->hfactor);
 +      if (ret)
 +              return ret;
 +
 +      ret = fimc_get_scaler_factor(sy, ty,  &sc->pre_vratio, &sc->vfactor);
 +      if (ret)
 +              return ret;
 +
 +      sc->pre_dst_width = sx / sc->pre_hratio;
 +      sc->pre_dst_height = sy / sc->pre_vratio;
 +
 +      if (variant->has_mainscaler_ext) {
 +              sc->main_hratio = (sx << 14) / (tx << sc->hfactor);
 +              sc->main_vratio = (sy << 14) / (ty << sc->vfactor);
 +      } else {
 +              sc->main_hratio = (sx << 8) / (tx << sc->hfactor);
 +              sc->main_vratio = (sy << 8) / (ty << sc->vfactor);
 +
 +      }
 +
 +      sc->scaleup_h = (tx >= sx) ? 1 : 0;
 +      sc->scaleup_v = (ty >= sy) ? 1 : 0;
 +
 +      /* check to see if input and output size/format differ */
 +      if (s_frame->fmt->color == d_frame->fmt->color
 +              && s_frame->width == d_frame->width
 +              && s_frame->height == d_frame->height)
 +              sc->copy_mode = 1;
 +      else
 +              sc->copy_mode = 0;
 +
 +      return 0;
 +}
 +
 +static irqreturn_t fimc_irq_handler(int irq, void *priv)
 +{
 +      struct fimc_dev *fimc = priv;
 +      struct fimc_ctx *ctx;
 +
 +      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);
 +
 +                      if (ctx->state & FIMC_CTX_SHUT) {
 +                              ctx->state &= ~FIMC_CTX_SHUT;
 +                              wake_up(&fimc->irq_queue);
 +                      }
 +                      return IRQ_HANDLED;
 +              }
 +      } else if (test_bit(ST_CAPT_PEND, &fimc->state)) {
 +              int last_buf = test_bit(ST_CAPT_JPEG, &fimc->state) &&
 +                              fimc->vid_cap.reqbufs_count == 1;
 +              fimc_capture_irq_handler(fimc, !last_buf);
 +      }
 +out:
 +      spin_unlock(&fimc->slock);
 +      return IRQ_HANDLED;
 +}
 +
 +/* The color format (colplanes, memplanes) must be already configured. */
 +int fimc_prepare_addr(struct fimc_ctx *ctx, struct vb2_buffer *vb,
 +                    struct fimc_frame *frame, struct fimc_addr *paddr)
 +{
 +      int ret = 0;
 +      u32 pix_size;
 +
 +      if (vb == NULL || frame == NULL)
 +              return -EINVAL;
 +
 +      pix_size = frame->width * frame->height;
 +
 +      dbg("memplanes= %d, colplanes= %d, pix_size= %d",
 +              frame->fmt->memplanes, frame->fmt->colplanes, pix_size);
 +
 +      paddr->y = vb2_dma_contig_plane_dma_addr(vb, 0);
 +
 +      if (frame->fmt->memplanes == 1) {
 +              switch (frame->fmt->colplanes) {
 +              case 1:
 +                      paddr->cb = 0;
 +                      paddr->cr = 0;
 +                      break;
 +              case 2:
 +                      /* decompose Y into Y/Cb */
 +                      paddr->cb = (u32)(paddr->y + pix_size);
 +                      paddr->cr = 0;
 +                      break;
 +              case 3:
 +                      paddr->cb = (u32)(paddr->y + pix_size);
 +                      /* decompose Y into Y/Cb/Cr */
 +                      if (FIMC_FMT_YCBCR420 == frame->fmt->color)
 +                              paddr->cr = (u32)(paddr->cb
 +                                              + (pix_size >> 2));
 +                      else /* 422 */
 +                              paddr->cr = (u32)(paddr->cb
 +                                              + (pix_size >> 1));
 +                      break;
 +              default:
 +                      return -EINVAL;
 +              }
 +      } else if (!frame->fmt->mdataplanes) {
 +              if (frame->fmt->memplanes >= 2)
 +                      paddr->cb = vb2_dma_contig_plane_dma_addr(vb, 1);
 +
 +              if (frame->fmt->memplanes == 3)
 +                      paddr->cr = vb2_dma_contig_plane_dma_addr(vb, 2);
 +      }
 +
 +      dbg("PHYS_ADDR: y= 0x%X  cb= 0x%X cr= 0x%X ret= %d",
 +          paddr->y, paddr->cb, paddr->cr, ret);
 +
 +      return ret;
 +}
 +
 +/* Set order for 1 and 2 plane YCBCR 4:2:2 formats. */
 +void fimc_set_yuv_order(struct fimc_ctx *ctx)
 +{
 +      /* The one only mode supported in SoC. */
 +      ctx->in_order_2p = FIMC_REG_CIOCTRL_ORDER422_2P_LSB_CRCB;
 +      ctx->out_order_2p = FIMC_REG_CIOCTRL_ORDER422_2P_LSB_CRCB;
 +
 +      /* Set order for 1 plane input formats. */
 +      switch (ctx->s_frame.fmt->color) {
 +      case FIMC_FMT_YCRYCB422:
 +              ctx->in_order_1p = FIMC_REG_MSCTRL_ORDER422_CBYCRY;
 +              break;
 +      case FIMC_FMT_CBYCRY422:
 +              ctx->in_order_1p = FIMC_REG_MSCTRL_ORDER422_YCRYCB;
 +              break;
 +      case FIMC_FMT_CRYCBY422:
 +              ctx->in_order_1p = FIMC_REG_MSCTRL_ORDER422_YCBYCR;
 +              break;
 +      case FIMC_FMT_YCBYCR422:
 +      default:
 +              ctx->in_order_1p = FIMC_REG_MSCTRL_ORDER422_CRYCBY;
 +              break;
 +      }
 +      dbg("ctx->in_order_1p= %d", ctx->in_order_1p);
 +
 +      switch (ctx->d_frame.fmt->color) {
 +      case FIMC_FMT_YCRYCB422:
 +              ctx->out_order_1p = FIMC_REG_CIOCTRL_ORDER422_CBYCRY;
 +              break;
 +      case FIMC_FMT_CBYCRY422:
 +              ctx->out_order_1p = FIMC_REG_CIOCTRL_ORDER422_YCRYCB;
 +              break;
 +      case FIMC_FMT_CRYCBY422:
 +              ctx->out_order_1p = FIMC_REG_CIOCTRL_ORDER422_YCBYCR;
 +              break;
 +      case FIMC_FMT_YCBYCR422:
 +      default:
 +              ctx->out_order_1p = FIMC_REG_CIOCTRL_ORDER422_CRYCBY;
 +              break;
 +      }
 +      dbg("ctx->out_order_1p= %d", ctx->out_order_1p);
 +}
 +
 +void fimc_prepare_dma_offset(struct fimc_ctx *ctx, struct fimc_frame *f)
 +{
 +      bool pix_hoff = ctx->fimc_dev->drv_data->dma_pix_hoff;
 +      u32 i, depth = 0;
 +
 +      for (i = 0; i < f->fmt->colplanes; i++)
 +              depth += f->fmt->depth[i];
 +
 +      f->dma_offset.y_h = f->offs_h;
 +      if (!pix_hoff)
 +              f->dma_offset.y_h *= (depth >> 3);
 +
 +      f->dma_offset.y_v = f->offs_v;
 +
 +      f->dma_offset.cb_h = f->offs_h;
 +      f->dma_offset.cb_v = f->offs_v;
 +
 +      f->dma_offset.cr_h = f->offs_h;
 +      f->dma_offset.cr_v = f->offs_v;
 +
 +      if (!pix_hoff) {
 +              if (f->fmt->colplanes == 3) {
 +                      f->dma_offset.cb_h >>= 1;
 +                      f->dma_offset.cr_h >>= 1;
 +              }
 +              if (f->fmt->color == FIMC_FMT_YCBCR420) {
 +                      f->dma_offset.cb_v >>= 1;
 +                      f->dma_offset.cr_v >>= 1;
 +              }
 +      }
 +
 +      dbg("in_offset: color= %d, y_h= %d, y_v= %d",
 +          f->fmt->color, f->dma_offset.y_h, f->dma_offset.y_v);
 +}
 +
 +static int fimc_set_color_effect(struct fimc_ctx *ctx, enum v4l2_colorfx colorfx)
 +{
 +      struct fimc_effect *effect = &ctx->effect;
 +
 +      switch (colorfx) {
 +      case V4L2_COLORFX_NONE:
 +              effect->type = FIMC_REG_CIIMGEFF_FIN_BYPASS;
 +              break;
 +      case V4L2_COLORFX_BW:
 +              effect->type = FIMC_REG_CIIMGEFF_FIN_ARBITRARY;
 +              effect->pat_cb = 128;
 +              effect->pat_cr = 128;
 +              break;
 +      case V4L2_COLORFX_SEPIA:
 +              effect->type = FIMC_REG_CIIMGEFF_FIN_ARBITRARY;
 +              effect->pat_cb = 115;
 +              effect->pat_cr = 145;
 +              break;
 +      case V4L2_COLORFX_NEGATIVE:
 +              effect->type = FIMC_REG_CIIMGEFF_FIN_NEGATIVE;
 +              break;
 +      case V4L2_COLORFX_EMBOSS:
 +              effect->type = FIMC_REG_CIIMGEFF_FIN_EMBOSSING;
 +              break;
 +      case V4L2_COLORFX_ART_FREEZE:
 +              effect->type = FIMC_REG_CIIMGEFF_FIN_ARTFREEZE;
 +              break;
 +      case V4L2_COLORFX_SILHOUETTE:
 +              effect->type = FIMC_REG_CIIMGEFF_FIN_SILHOUETTE;
 +              break;
 +      case V4L2_COLORFX_SET_CBCR:
 +              effect->type = FIMC_REG_CIIMGEFF_FIN_ARBITRARY;
 +              effect->pat_cb = ctx->ctrls.colorfx_cbcr->val >> 8;
 +              effect->pat_cr = ctx->ctrls.colorfx_cbcr->val & 0xff;
 +              break;
 +      default:
 +              return -EINVAL;
 +      }
 +
 +      return 0;
 +}
 +
 +/*
 + * V4L2 controls handling
 + */
 +#define ctrl_to_ctx(__ctrl) \
 +      container_of((__ctrl)->handler, struct fimc_ctx, ctrls.handler)
 +
 +static int __fimc_s_ctrl(struct fimc_ctx *ctx, struct v4l2_ctrl *ctrl)
 +{
 +      struct fimc_dev *fimc = ctx->fimc_dev;
 +      const struct fimc_variant *variant = fimc->variant;
 +      int ret = 0;
 +
 +      if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE)
 +              return 0;
 +
 +      switch (ctrl->id) {
 +      case V4L2_CID_HFLIP:
 +              ctx->hflip = ctrl->val;
 +              break;
 +
 +      case V4L2_CID_VFLIP:
 +              ctx->vflip = ctrl->val;
 +              break;
 +
 +      case V4L2_CID_ROTATE:
 +              if (fimc_capture_pending(fimc)) {
 +                      ret = fimc_check_scaler_ratio(ctx, ctx->s_frame.width,
 +                                      ctx->s_frame.height, ctx->d_frame.width,
 +                                      ctx->d_frame.height, ctrl->val);
 +                      if (ret)
 +                              return -EINVAL;
 +              }
 +              if ((ctrl->val == 90 || ctrl->val == 270) &&
 +                  !variant->has_out_rot)
 +                      return -EINVAL;
 +
 +              ctx->rotation = ctrl->val;
 +              break;
 +
 +      case V4L2_CID_ALPHA_COMPONENT:
 +              ctx->d_frame.alpha = ctrl->val;
 +              break;
 +
 +      case V4L2_CID_COLORFX:
 +              ret = fimc_set_color_effect(ctx, ctrl->val);
 +              if (ret)
 +                      return ret;
 +              break;
 +      }
 +
 +      ctx->state |= FIMC_PARAMS;
 +      set_bit(ST_CAPT_APPLY_CFG, &fimc->state);
 +      return 0;
 +}
 +
 +static int fimc_s_ctrl(struct v4l2_ctrl *ctrl)
 +{
 +      struct fimc_ctx *ctx = ctrl_to_ctx(ctrl);
 +      unsigned long flags;
 +      int ret;
 +
 +      spin_lock_irqsave(&ctx->fimc_dev->slock, flags);
 +      ret = __fimc_s_ctrl(ctx, ctrl);
 +      spin_unlock_irqrestore(&ctx->fimc_dev->slock, flags);
 +
 +      return ret;
 +}
 +
 +static const struct v4l2_ctrl_ops fimc_ctrl_ops = {
 +      .s_ctrl = fimc_s_ctrl,
 +};
 +
 +int fimc_ctrls_create(struct fimc_ctx *ctx)
 +{
 +      unsigned int max_alpha = fimc_get_alpha_mask(ctx->d_frame.fmt);
 +      struct fimc_ctrls *ctrls = &ctx->ctrls;
 +      struct v4l2_ctrl_handler *handler = &ctrls->handler;
 +
 +      if (ctx->ctrls.ready)
 +              return 0;
 +
 +      v4l2_ctrl_handler_init(handler, 6);
 +
 +      ctrls->rotate = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops,
 +                                      V4L2_CID_ROTATE, 0, 270, 90, 0);
 +      ctrls->hflip = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops,
 +                                      V4L2_CID_HFLIP, 0, 1, 1, 0);
 +      ctrls->vflip = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops,
 +                                      V4L2_CID_VFLIP, 0, 1, 1, 0);
 +
 +      if (ctx->fimc_dev->drv_data->alpha_color)
 +              ctrls->alpha = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops,
 +                                      V4L2_CID_ALPHA_COMPONENT,
 +                                      0, max_alpha, 1, 0);
 +      else
 +              ctrls->alpha = NULL;
 +
 +      ctrls->colorfx = v4l2_ctrl_new_std_menu(handler, &fimc_ctrl_ops,
 +                              V4L2_CID_COLORFX, V4L2_COLORFX_SET_CBCR,
 +                              ~0x983f, V4L2_COLORFX_NONE);
 +
 +      ctrls->colorfx_cbcr = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops,
 +                              V4L2_CID_COLORFX_CBCR, 0, 0xffff, 1, 0);
 +
 +      ctx->effect.type = FIMC_REG_CIIMGEFF_FIN_BYPASS;
 +
 +      if (!handler->error) {
 +              v4l2_ctrl_cluster(2, &ctrls->colorfx);
 +              ctrls->ready = true;
 +      }
 +
 +      return handler->error;
 +}
 +
 +void fimc_ctrls_delete(struct fimc_ctx *ctx)
 +{
 +      struct fimc_ctrls *ctrls = &ctx->ctrls;
 +
 +      if (ctrls->ready) {
 +              v4l2_ctrl_handler_free(&ctrls->handler);
 +              ctrls->ready = false;
 +              ctrls->alpha = NULL;
 +      }
 +}
 +
 +void fimc_ctrls_activate(struct fimc_ctx *ctx, bool active)
 +{
 +      unsigned int has_alpha = ctx->d_frame.fmt->flags & FMT_HAS_ALPHA;
 +      struct fimc_ctrls *ctrls = &ctx->ctrls;
 +
 +      if (!ctrls->ready)
 +              return;
 +
 +      mutex_lock(ctrls->handler.lock);
 +      v4l2_ctrl_activate(ctrls->rotate, active);
 +      v4l2_ctrl_activate(ctrls->hflip, active);
 +      v4l2_ctrl_activate(ctrls->vflip, active);
 +      v4l2_ctrl_activate(ctrls->colorfx, active);
 +      if (ctrls->alpha)
 +              v4l2_ctrl_activate(ctrls->alpha, active && has_alpha);
 +
 +      if (active) {
 +              fimc_set_color_effect(ctx, ctrls->colorfx->cur.val);
 +              ctx->rotation = ctrls->rotate->val;
 +              ctx->hflip    = ctrls->hflip->val;
 +              ctx->vflip    = ctrls->vflip->val;
 +      } else {
 +              ctx->effect.type = FIMC_REG_CIIMGEFF_FIN_BYPASS;
 +              ctx->rotation = 0;
 +              ctx->hflip    = 0;
 +              ctx->vflip    = 0;
 +      }
 +      mutex_unlock(ctrls->handler.lock);
 +}
 +
 +/* Update maximum value of the alpha color control */
 +void fimc_alpha_ctrl_update(struct fimc_ctx *ctx)
 +{
 +      struct fimc_dev *fimc = ctx->fimc_dev;
 +      struct v4l2_ctrl *ctrl = ctx->ctrls.alpha;
 +
 +      if (ctrl == NULL || !fimc->drv_data->alpha_color)
 +              return;
 +
 +      v4l2_ctrl_lock(ctrl);
 +      ctrl->maximum = fimc_get_alpha_mask(ctx->d_frame.fmt);
 +
 +      if (ctrl->cur.val > ctrl->maximum)
 +              ctrl->cur.val = ctrl->maximum;
 +
 +      v4l2_ctrl_unlock(ctrl);
 +}
 +
 +void __fimc_get_format(struct fimc_frame *frame, struct v4l2_format *f)
 +{
 +      struct v4l2_pix_format_mplane *pixm = &f->fmt.pix_mp;
 +      int i;
 +
 +      pixm->width = frame->o_width;
 +      pixm->height = frame->o_height;
 +      pixm->field = V4L2_FIELD_NONE;
 +      pixm->pixelformat = frame->fmt->fourcc;
 +      pixm->colorspace = V4L2_COLORSPACE_JPEG;
 +      pixm->num_planes = frame->fmt->memplanes;
 +
 +      for (i = 0; i < pixm->num_planes; ++i) {
 +              pixm->plane_fmt[i].bytesperline = frame->bytesperline[i];
 +              pixm->plane_fmt[i].sizeimage = frame->payload[i];
 +      }
 +}
 +
 +/**
 + * fimc_adjust_mplane_format - adjust bytesperline/sizeimage for each plane
 + * @fmt: fimc pixel format description (input)
 + * @width: requested pixel width
 + * @height: requested pixel height
 + * @pix: multi-plane format to adjust
 + */
 +void fimc_adjust_mplane_format(struct fimc_fmt *fmt, u32 width, u32 height,
 +                             struct v4l2_pix_format_mplane *pix)
 +{
 +      u32 bytesperline = 0;
 +      int i;
 +
 +      pix->colorspace = V4L2_COLORSPACE_JPEG;
 +      pix->field = V4L2_FIELD_NONE;
 +      pix->num_planes = fmt->memplanes;
 +      pix->pixelformat = fmt->fourcc;
 +      pix->height = height;
 +      pix->width = width;
 +
 +      for (i = 0; i < pix->num_planes; ++i) {
 +              struct v4l2_plane_pix_format *plane_fmt = &pix->plane_fmt[i];
 +              u32 bpl = plane_fmt->bytesperline;
 +
 +              if (fmt->colplanes > 1 && (bpl == 0 || bpl < pix->width))
 +                      bpl = pix->width; /* Planar */
 +
 +              if (fmt->colplanes == 1 && /* Packed */
 +                  (bpl == 0 || ((bpl * 8) / fmt->depth[i]) < pix->width))
 +                      bpl = (pix->width * fmt->depth[0]) / 8;
 +              /*
 +               * Currently bytesperline for each plane is same, except
 +               * V4L2_PIX_FMT_YUV420M format. This calculation may need
 +               * to be changed when other multi-planar formats are added
 +               * to the fimc_formats[] array.
 +               */
 +              if (i == 0)
 +                      bytesperline = bpl;
 +              else if (i == 1 && fmt->memplanes == 3)
 +                      bytesperline /= 2;
 +
 +              plane_fmt->bytesperline = bytesperline;
 +              plane_fmt->sizeimage = max((pix->width * pix->height *
 +                                 fmt->depth[i]) / 8, plane_fmt->sizeimage);
 +      }
 +}
 +
 +/**
 + * fimc_find_format - lookup fimc color format by fourcc or media bus format
 + * @pixelformat: fourcc to match, ignored if null
 + * @mbus_code: media bus code to match, ignored if null
 + * @mask: the color flags to match
 + * @index: offset in the fimc_formats array, ignored if negative
 + */
 +struct fimc_fmt *fimc_find_format(const u32 *pixelformat, const u32 *mbus_code,
 +                                unsigned int mask, int index)
 +{
 +      struct fimc_fmt *fmt, *def_fmt = NULL;
 +      unsigned int i;
 +      int id = 0;
 +
 +      if (index >= (int)ARRAY_SIZE(fimc_formats))
 +              return NULL;
 +
 +      for (i = 0; i < ARRAY_SIZE(fimc_formats); ++i) {
 +              fmt = &fimc_formats[i];
 +              if (!(fmt->flags & mask))
 +                      continue;
 +              if (pixelformat && fmt->fourcc == *pixelformat)
 +                      return fmt;
 +              if (mbus_code && fmt->mbus_code == *mbus_code)
 +                      return fmt;
 +              if (index == id)
 +                      def_fmt = fmt;
 +              id++;
 +      }
 +      return def_fmt;
 +}
 +
 +static void fimc_clk_put(struct fimc_dev *fimc)
 +{
 +      int i;
 +      for (i = 0; i < MAX_FIMC_CLOCKS; i++) {
 +              if (IS_ERR(fimc->clock[i]))
 +                      continue;
 +              clk_unprepare(fimc->clock[i]);
 +              clk_put(fimc->clock[i]);
 +              fimc->clock[i] = ERR_PTR(-EINVAL);
 +      }
 +}
 +
 +static int fimc_clk_get(struct fimc_dev *fimc)
 +{
 +      int i, ret;
 +
 +      for (i = 0; i < MAX_FIMC_CLOCKS; i++)
 +              fimc->clock[i] = ERR_PTR(-EINVAL);
 +
 +      for (i = 0; i < MAX_FIMC_CLOCKS; i++) {
 +              fimc->clock[i] = clk_get(&fimc->pdev->dev, fimc_clocks[i]);
 +              if (IS_ERR(fimc->clock[i])) {
 +                      ret = PTR_ERR(fimc->clock[i]);
 +                      goto err;
 +              }
 +              ret = clk_prepare(fimc->clock[i]);
 +              if (ret < 0) {
 +                      clk_put(fimc->clock[i]);
 +                      fimc->clock[i] = ERR_PTR(-EINVAL);
 +                      goto err;
 +              }
 +      }
 +      return 0;
 +err:
 +      fimc_clk_put(fimc);
 +      dev_err(&fimc->pdev->dev, "failed to get clock: %s\n",
 +              fimc_clocks[i]);
 +      return -ENXIO;
 +}
 +
 +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)
 +{
++      struct fimc_ctx *ctx;
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&fimc->slock, flags);
 +      /* Clear for full H/W setup in first run after resume */
++      ctx = fimc->m2m.ctx;
 +      fimc->m2m.ctx = NULL;
 +      spin_unlock_irqrestore(&fimc->slock, flags);
 +
 +      if (test_and_clear_bit(ST_M2M_SUSPENDED, &fimc->state))
++              fimc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR);
++
 +      return 0;
 +}
 +
 +static const struct of_device_id fimc_of_match[];
 +
 +static int fimc_parse_dt(struct fimc_dev *fimc, u32 *clk_freq)
 +{
 +      struct device *dev = &fimc->pdev->dev;
 +      struct device_node *node = dev->of_node;
 +      const struct of_device_id *of_id;
 +      struct fimc_variant *v;
 +      struct fimc_pix_limit *lim;
 +      u32 args[FIMC_PIX_LIMITS_MAX];
 +      int ret;
 +
 +      if (of_property_read_bool(node, "samsung,lcd-wb"))
 +              return -ENODEV;
 +
 +      v = devm_kzalloc(dev, sizeof(*v) + sizeof(*lim), GFP_KERNEL);
 +      if (!v)
 +              return -ENOMEM;
 +
 +      of_id = of_match_node(fimc_of_match, node);
 +      if (!of_id)
 +              return -EINVAL;
 +      fimc->drv_data = of_id->data;
 +      ret = of_property_read_u32_array(node, "samsung,pix-limits",
 +                                       args, FIMC_PIX_LIMITS_MAX);
 +      if (ret < 0)
 +              return ret;
 +
 +      lim = (struct fimc_pix_limit *)&v[1];
 +
 +      lim->scaler_en_w = args[0];
 +      lim->scaler_dis_w = args[1];
 +      lim->out_rot_en_w = args[2];
 +      lim->out_rot_dis_w = args[3];
 +      v->pix_limit = lim;
 +
 +      ret = of_property_read_u32_array(node, "samsung,min-pix-sizes",
 +                                                              args, 2);
 +      v->min_inp_pixsize = ret ? FIMC_DEF_MIN_SIZE : args[0];
 +      v->min_out_pixsize = ret ? FIMC_DEF_MIN_SIZE : args[1];
 +      ret = of_property_read_u32_array(node, "samsung,min-pix-alignment",
 +                                                              args, 2);
 +      v->min_vsize_align = ret ? FIMC_DEF_HEIGHT_ALIGN : args[0];
 +      v->hor_offs_align = ret ? FIMC_DEF_HOR_OFFS_ALIGN : args[1];
 +
 +      ret = of_property_read_u32(node, "samsung,rotators", &args[1]);
 +      v->has_inp_rot = ret ? 1 : args[1] & 0x01;
 +      v->has_out_rot = ret ? 1 : args[1] & 0x10;
 +      v->has_mainscaler_ext = of_property_read_bool(node,
 +                                      "samsung,mainscaler-ext");
 +
 +      v->has_isp_wb = of_property_read_bool(node, "samsung,isp-wb");
 +      v->has_cam_if = of_property_read_bool(node, "samsung,cam-if");
 +      of_property_read_u32(node, "clock-frequency", clk_freq);
 +      fimc->id = of_alias_get_id(node, "fimc");
 +
 +      fimc->variant = v;
 +      return 0;
 +}
 +
 +static int fimc_probe(struct platform_device *pdev)
 +{
 +      struct device *dev = &pdev->dev;
 +      u32 lclk_freq = 0;
 +      struct fimc_dev *fimc;
 +      struct resource *res;
 +      int ret = 0;
 +
 +      fimc = devm_kzalloc(dev, sizeof(*fimc), GFP_KERNEL);
 +      if (!fimc)
 +              return -ENOMEM;
 +
 +      fimc->pdev = pdev;
 +
 +      if (dev->of_node) {
 +              ret = fimc_parse_dt(fimc, &lclk_freq);
 +              if (ret < 0)
 +                      return ret;
 +      } else {
 +              fimc->drv_data = fimc_get_drvdata(pdev);
 +              fimc->id = pdev->id;
 +      }
 +      if (!fimc->drv_data || fimc->id >= fimc->drv_data->num_entities ||
 +          fimc->id < 0) {
 +              dev_err(dev, "Invalid driver data or device id (%d/%d)\n",
 +                      fimc->id, fimc->drv_data->num_entities);
 +              return -EINVAL;
 +      }
 +      if (!dev->of_node)
 +              fimc->variant = fimc->drv_data->variant[fimc->id];
 +
 +      init_waitqueue_head(&fimc->irq_queue);
 +      spin_lock_init(&fimc->slock);
 +      mutex_init(&fimc->lock);
 +
 +      fimc->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node,
 +                                              "samsung,sysreg");
 +      if (IS_ERR(fimc->sysreg))
 +              return PTR_ERR(fimc->sysreg);
 +
 +      res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 +      fimc->regs = devm_ioremap_resource(dev, res);
 +      if (IS_ERR(fimc->regs))
 +              return PTR_ERR(fimc->regs);
 +
 +      res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
 +      if (res == NULL) {
 +              dev_err(dev, "Failed to get IRQ resource\n");
 +              return -ENXIO;
 +      }
 +
 +      ret = fimc_clk_get(fimc);
 +      if (ret)
 +              return ret;
 +
 +      if (lclk_freq == 0)
 +              lclk_freq = fimc->drv_data->lclk_frequency;
 +
 +      ret = clk_set_rate(fimc->clock[CLK_BUS], lclk_freq);
 +      if (ret < 0)
 +              return ret;
 +
 +      ret = clk_enable(fimc->clock[CLK_BUS]);
 +      if (ret < 0)
 +              return ret;
 +
 +      ret = devm_request_irq(dev, res->start, fimc_irq_handler,
 +                             0, dev_name(dev), fimc);
 +      if (ret) {
 +              dev_err(dev, "failed to install irq (%d)\n", ret);
 +              goto err_clk;
 +      }
 +
 +      ret = fimc_initialize_capture_subdev(fimc);
 +      if (ret)
 +              goto err_clk;
 +
 +      platform_set_drvdata(pdev, fimc);
 +      pm_runtime_enable(dev);
 +      ret = pm_runtime_get_sync(dev);
 +      if (ret < 0)
 +              goto err_sd;
 +      /* Initialize contiguous memory allocator */
 +      fimc->alloc_ctx = vb2_dma_contig_init_ctx(dev);
 +      if (IS_ERR(fimc->alloc_ctx)) {
 +              ret = PTR_ERR(fimc->alloc_ctx);
 +              goto err_pm;
 +      }
 +
 +      dev_dbg(dev, "FIMC.%d registered successfully\n", fimc->id);
 +
 +      pm_runtime_put(dev);
 +      return 0;
 +err_pm:
 +      pm_runtime_put(dev);
 +err_sd:
 +      fimc_unregister_capture_subdev(fimc);
 +err_clk:
 +      clk_disable(fimc->clock[CLK_BUS]);
 +      fimc_clk_put(fimc);
 +      return ret;
 +}
 +
 +static int fimc_runtime_resume(struct device *dev)
 +{
 +      struct fimc_dev *fimc = dev_get_drvdata(dev);
 +
 +      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);
 +
 +      /* Resume the capture or mem-to-mem device */
 +      if (fimc_capture_busy(fimc))
 +              return fimc_capture_resume(fimc);
 +
 +      return fimc_m2m_resume(fimc);
 +}
 +
 +static int fimc_runtime_suspend(struct device *dev)
 +{
 +      struct fimc_dev *fimc = dev_get_drvdata(dev);
 +      int ret = 0;
 +
 +      if (fimc_capture_busy(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_busy(fimc))) {
 +              spin_unlock_irqrestore(&fimc->slock, flags);
 +              return 0;
 +      }
 +      fimc_hw_reset(fimc);
 +      spin_unlock_irqrestore(&fimc->slock, flags);
 +
 +      if (fimc_capture_busy(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_busy(fimc))
 +              return fimc_capture_suspend(fimc);
 +
 +      return fimc_m2m_suspend(fimc);
 +}
 +#endif /* CONFIG_PM_SLEEP */
 +
 +static int fimc_remove(struct platform_device *pdev)
 +{
 +      struct fimc_dev *fimc = platform_get_drvdata(pdev);
 +
 +      pm_runtime_disable(&pdev->dev);
 +      pm_runtime_set_suspended(&pdev->dev);
 +
 +      fimc_unregister_capture_subdev(fimc);
 +      vb2_dma_contig_cleanup_ctx(fimc->alloc_ctx);
 +
 +      clk_disable(fimc->clock[CLK_BUS]);
 +      fimc_clk_put(fimc);
 +
 +      dev_info(&pdev->dev, "driver unloaded\n");
 +      return 0;
 +}
 +
 +/* Image pixel limits, similar across several FIMC HW revisions. */
 +static const struct fimc_pix_limit s5p_pix_limit[4] = {
 +      [0] = {
 +              .scaler_en_w    = 3264,
 +              .scaler_dis_w   = 8192,
 +              .out_rot_en_w   = 1920,
 +              .out_rot_dis_w  = 4224,
 +      },
 +      [1] = {
 +              .scaler_en_w    = 4224,
 +              .scaler_dis_w   = 8192,
 +              .out_rot_en_w   = 1920,
 +              .out_rot_dis_w  = 4224,
 +      },
 +      [2] = {
 +              .scaler_en_w    = 1920,
 +              .scaler_dis_w   = 8192,
 +              .out_rot_en_w   = 1280,
 +              .out_rot_dis_w  = 1920,
 +      },
 +      [3] = {
 +              .scaler_en_w    = 1920,
 +              .scaler_dis_w   = 8192,
 +              .in_rot_en_h    = 1366,
 +              .in_rot_dis_w   = 8192,
 +              .out_rot_en_w   = 1366,
 +              .out_rot_dis_w  = 1920,
 +      },
 +};
 +
 +static const struct fimc_variant fimc0_variant_s5p = {
 +      .has_inp_rot     = 1,
 +      .has_out_rot     = 1,
 +      .has_cam_if      = 1,
 +      .min_inp_pixsize = 16,
 +      .min_out_pixsize = 16,
 +      .hor_offs_align  = 8,
 +      .min_vsize_align = 16,
 +      .pix_limit       = &s5p_pix_limit[0],
 +};
 +
 +static const struct fimc_variant fimc2_variant_s5p = {
 +      .has_cam_if      = 1,
 +      .min_inp_pixsize = 16,
 +      .min_out_pixsize = 16,
 +      .hor_offs_align  = 8,
 +      .min_vsize_align = 16,
 +      .pix_limit       = &s5p_pix_limit[1],
 +};
 +
 +static const struct fimc_variant fimc0_variant_s5pv210 = {
 +      .has_inp_rot     = 1,
 +      .has_out_rot     = 1,
 +      .has_cam_if      = 1,
 +      .min_inp_pixsize = 16,
 +      .min_out_pixsize = 16,
 +      .hor_offs_align  = 8,
 +      .min_vsize_align = 16,
 +      .pix_limit       = &s5p_pix_limit[1],
 +};
 +
 +static const struct fimc_variant fimc1_variant_s5pv210 = {
 +      .has_inp_rot     = 1,
 +      .has_out_rot     = 1,
 +      .has_cam_if      = 1,
 +      .has_mainscaler_ext = 1,
 +      .min_inp_pixsize = 16,
 +      .min_out_pixsize = 16,
 +      .hor_offs_align  = 1,
 +      .min_vsize_align = 1,
 +      .pix_limit       = &s5p_pix_limit[2],
 +};
 +
 +static const struct fimc_variant fimc2_variant_s5pv210 = {
 +      .has_cam_if      = 1,
 +      .min_inp_pixsize = 16,
 +      .min_out_pixsize = 16,
 +      .hor_offs_align  = 8,
 +      .min_vsize_align = 16,
 +      .pix_limit       = &s5p_pix_limit[2],
 +};
 +
 +static const struct fimc_variant fimc0_variant_exynos4210 = {
 +      .has_inp_rot     = 1,
 +      .has_out_rot     = 1,
 +      .has_cam_if      = 1,
 +      .has_mainscaler_ext = 1,
 +      .min_inp_pixsize = 16,
 +      .min_out_pixsize = 16,
 +      .hor_offs_align  = 2,
 +      .min_vsize_align = 1,
 +      .pix_limit       = &s5p_pix_limit[1],
 +};
 +
 +static const struct fimc_variant fimc3_variant_exynos4210 = {
 +      .has_mainscaler_ext = 1,
 +      .min_inp_pixsize = 16,
 +      .min_out_pixsize = 16,
 +      .hor_offs_align  = 2,
 +      .min_vsize_align = 1,
 +      .pix_limit       = &s5p_pix_limit[3],
 +};
 +
 +/* S5PC100 */
 +static const struct fimc_drvdata fimc_drvdata_s5p = {
 +      .variant = {
 +              [0] = &fimc0_variant_s5p,
 +              [1] = &fimc0_variant_s5p,
 +              [2] = &fimc2_variant_s5p,
 +      },
 +      .num_entities   = 3,
 +      .lclk_frequency = 133000000UL,
 +      .out_buf_count  = 4,
 +};
 +
 +/* S5PV210, S5PC110 */
 +static const struct fimc_drvdata fimc_drvdata_s5pv210 = {
 +      .variant = {
 +              [0] = &fimc0_variant_s5pv210,
 +              [1] = &fimc1_variant_s5pv210,
 +              [2] = &fimc2_variant_s5pv210,
 +      },
 +      .num_entities   = 3,
 +      .lclk_frequency = 166000000UL,
 +      .out_buf_count  = 4,
 +      .dma_pix_hoff   = 1,
 +};
 +
 +/* EXYNOS4210, S5PV310, S5PC210 */
 +static const struct fimc_drvdata fimc_drvdata_exynos4210 = {
 +      .variant = {
 +              [0] = &fimc0_variant_exynos4210,
 +              [1] = &fimc0_variant_exynos4210,
 +              [2] = &fimc0_variant_exynos4210,
 +              [3] = &fimc3_variant_exynos4210,
 +      },
 +      .num_entities   = 4,
 +      .lclk_frequency = 166000000UL,
 +      .dma_pix_hoff   = 1,
 +      .cistatus2      = 1,
 +      .alpha_color    = 1,
 +      .out_buf_count  = 32,
 +};
 +
 +/* EXYNOS4212, EXYNOS4412 */
 +static const struct fimc_drvdata fimc_drvdata_exynos4x12 = {
 +      .num_entities   = 4,
 +      .lclk_frequency = 166000000UL,
 +      .dma_pix_hoff   = 1,
 +      .cistatus2      = 1,
 +      .alpha_color    = 1,
 +      .out_buf_count  = 32,
 +};
 +
 +static const struct platform_device_id fimc_driver_ids[] = {
 +      {
 +              .name           = "s5p-fimc",
 +              .driver_data    = (unsigned long)&fimc_drvdata_s5p,
 +      }, {
 +              .name           = "s5pv210-fimc",
 +              .driver_data    = (unsigned long)&fimc_drvdata_s5pv210,
 +      }, {
 +              .name           = "exynos4-fimc",
 +              .driver_data    = (unsigned long)&fimc_drvdata_exynos4210,
 +      }, {
 +              .name           = "exynos4x12-fimc",
 +              .driver_data    = (unsigned long)&fimc_drvdata_exynos4x12,
 +      },
 +      { },
 +};
 +
 +static const struct of_device_id fimc_of_match[] = {
 +      {
 +              .compatible = "samsung,s5pv210-fimc",
 +              .data = &fimc_drvdata_s5pv210,
 +      }, {
 +              .compatible = "samsung,exynos4210-fimc",
 +              .data = &fimc_drvdata_exynos4210,
 +      }, {
 +              .compatible = "samsung,exynos4212-fimc",
 +              .data = &fimc_drvdata_exynos4x12,
 +      },
 +      { /* sentinel */ },
 +};
 +
 +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         = fimc_remove,
 +      .id_table       = fimc_driver_ids,
 +      .driver = {
 +              .of_match_table = fimc_of_match,
 +              .name           = FIMC_MODULE_NAME,
 +              .owner          = THIS_MODULE,
 +              .pm             = &fimc_pm_ops,
 +      }
 +};
 +
 +int __init fimc_register_driver(void)
 +{
 +      return platform_driver_register(&fimc_driver);
 +}
 +
 +void __exit fimc_unregister_driver(void)
 +{
 +      platform_driver_unregister(&fimc_driver);
 +}
index f0af0754a7b46a04cee5f41552ede4d8bc2e96ed,0000000000000000000000000000000000000000..ac9663ce2a4927a0a3249bde33107fb1a17a9783
mode 100644,000000..100644
--- /dev/null
@@@ -1,302 -1,0 +1,302 @@@
-       unsigned int i = ARRAY_SIZE(src_pixfmt_map);
 +/*
 + * Register interface file for EXYNOS FIMC-LITE (camera interface) driver
 + *
 + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
 + * Sylwester Nawrocki <s.nawrocki@samsung.com>
 + *
 + * This program is free software; you can redistribute it and/or modify
 + * it under the terms of the GNU General Public License version 2 as
 + * published by the Free Software Foundation.
 +*/
 +
 +#include <linux/io.h>
 +#include <linux/delay.h>
 +#include <media/s5p_fimc.h>
 +
 +#include "fimc-lite-reg.h"
 +#include "fimc-lite.h"
 +#include "fimc-core.h"
 +
 +#define FLITE_RESET_TIMEOUT 50 /* in ms */
 +
 +void flite_hw_reset(struct fimc_lite *dev)
 +{
 +      unsigned long end = jiffies + msecs_to_jiffies(FLITE_RESET_TIMEOUT);
 +      u32 cfg;
 +
 +      cfg = readl(dev->regs + FLITE_REG_CIGCTRL);
 +      cfg |= FLITE_REG_CIGCTRL_SWRST_REQ;
 +      writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
 +
 +      while (time_is_after_jiffies(end)) {
 +              cfg = readl(dev->regs + FLITE_REG_CIGCTRL);
 +              if (cfg & FLITE_REG_CIGCTRL_SWRST_RDY)
 +                      break;
 +              usleep_range(1000, 5000);
 +      }
 +
 +      cfg |= FLITE_REG_CIGCTRL_SWRST;
 +      writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
 +}
 +
 +void flite_hw_clear_pending_irq(struct fimc_lite *dev)
 +{
 +      u32 cfg = readl(dev->regs + FLITE_REG_CISTATUS);
 +      cfg &= ~FLITE_REG_CISTATUS_IRQ_CAM;
 +      writel(cfg, dev->regs + FLITE_REG_CISTATUS);
 +}
 +
 +u32 flite_hw_get_interrupt_source(struct fimc_lite *dev)
 +{
 +      u32 intsrc = readl(dev->regs + FLITE_REG_CISTATUS);
 +      return intsrc & FLITE_REG_CISTATUS_IRQ_MASK;
 +}
 +
 +void flite_hw_clear_last_capture_end(struct fimc_lite *dev)
 +{
 +
 +      u32 cfg = readl(dev->regs + FLITE_REG_CISTATUS2);
 +      cfg &= ~FLITE_REG_CISTATUS2_LASTCAPEND;
 +      writel(cfg, dev->regs + FLITE_REG_CISTATUS2);
 +}
 +
 +void flite_hw_set_interrupt_mask(struct fimc_lite *dev)
 +{
 +      u32 cfg, intsrc;
 +
 +      /* Select interrupts to be enabled for each output mode */
 +      if (atomic_read(&dev->out_path) == FIMC_IO_DMA) {
 +              intsrc = FLITE_REG_CIGCTRL_IRQ_OVFEN |
 +                       FLITE_REG_CIGCTRL_IRQ_LASTEN |
 +                       FLITE_REG_CIGCTRL_IRQ_STARTEN;
 +      } else {
 +              /* An output to the FIMC-IS */
 +              intsrc = FLITE_REG_CIGCTRL_IRQ_OVFEN |
 +                       FLITE_REG_CIGCTRL_IRQ_LASTEN;
 +      }
 +
 +      cfg = readl(dev->regs + FLITE_REG_CIGCTRL);
 +      cfg |= FLITE_REG_CIGCTRL_IRQ_DISABLE_MASK;
 +      cfg &= ~intsrc;
 +      writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
 +}
 +
 +void flite_hw_capture_start(struct fimc_lite *dev)
 +{
 +      u32 cfg = readl(dev->regs + FLITE_REG_CIIMGCPT);
 +      cfg |= FLITE_REG_CIIMGCPT_IMGCPTEN;
 +      writel(cfg, dev->regs + FLITE_REG_CIIMGCPT);
 +}
 +
 +void flite_hw_capture_stop(struct fimc_lite *dev)
 +{
 +      u32 cfg = readl(dev->regs + FLITE_REG_CIIMGCPT);
 +      cfg &= ~FLITE_REG_CIIMGCPT_IMGCPTEN;
 +      writel(cfg, dev->regs + FLITE_REG_CIIMGCPT);
 +}
 +
 +/*
 + * Test pattern (color bars) enable/disable. External sensor
 + * pixel clock must be active for the test pattern to work.
 + */
 +void flite_hw_set_test_pattern(struct fimc_lite *dev, bool on)
 +{
 +      u32 cfg = readl(dev->regs + FLITE_REG_CIGCTRL);
 +      if (on)
 +              cfg |= FLITE_REG_CIGCTRL_TEST_PATTERN_COLORBAR;
 +      else
 +              cfg &= ~FLITE_REG_CIGCTRL_TEST_PATTERN_COLORBAR;
 +      writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
 +}
 +
 +static const u32 src_pixfmt_map[8][3] = {
 +      { V4L2_MBUS_FMT_YUYV8_2X8, FLITE_REG_CISRCSIZE_ORDER422_IN_YCBYCR,
 +        FLITE_REG_CIGCTRL_YUV422_1P },
 +      { V4L2_MBUS_FMT_YVYU8_2X8, FLITE_REG_CISRCSIZE_ORDER422_IN_YCRYCB,
 +        FLITE_REG_CIGCTRL_YUV422_1P },
 +      { V4L2_MBUS_FMT_UYVY8_2X8, FLITE_REG_CISRCSIZE_ORDER422_IN_CBYCRY,
 +        FLITE_REG_CIGCTRL_YUV422_1P },
 +      { V4L2_MBUS_FMT_VYUY8_2X8, FLITE_REG_CISRCSIZE_ORDER422_IN_CRYCBY,
 +        FLITE_REG_CIGCTRL_YUV422_1P },
 +      { V4L2_MBUS_FMT_SGRBG8_1X8, 0, FLITE_REG_CIGCTRL_RAW8 },
 +      { V4L2_MBUS_FMT_SGRBG10_1X10, 0, FLITE_REG_CIGCTRL_RAW10 },
 +      { V4L2_MBUS_FMT_SGRBG12_1X12, 0, FLITE_REG_CIGCTRL_RAW12 },
 +      { V4L2_MBUS_FMT_JPEG_1X8, 0, FLITE_REG_CIGCTRL_USER(1) },
 +};
 +
 +/* Set camera input pixel format and resolution */
 +void flite_hw_set_source_format(struct fimc_lite *dev, struct flite_frame *f)
 +{
 +      enum v4l2_mbus_pixelcode pixelcode = dev->fmt->mbus_code;
-       while (i-- >= 0) {
++      int i = ARRAY_SIZE(src_pixfmt_map);
 +      u32 cfg;
 +
-       unsigned int i = ARRAY_SIZE(pixcode);
++      while (--i >= 0) {
 +              if (src_pixfmt_map[i][0] == pixelcode)
 +                      break;
 +      }
 +
 +      if (i == 0 && src_pixfmt_map[i][0] != pixelcode) {
 +              v4l2_err(&dev->vfd,
 +                       "Unsupported pixel code, falling back to %#08x\n",
 +                       src_pixfmt_map[i][0]);
 +      }
 +
 +      cfg = readl(dev->regs + FLITE_REG_CIGCTRL);
 +      cfg &= ~FLITE_REG_CIGCTRL_FMT_MASK;
 +      cfg |= src_pixfmt_map[i][2];
 +      writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
 +
 +      cfg = readl(dev->regs + FLITE_REG_CISRCSIZE);
 +      cfg &= ~(FLITE_REG_CISRCSIZE_ORDER422_MASK |
 +               FLITE_REG_CISRCSIZE_SIZE_CAM_MASK);
 +      cfg |= (f->f_width << 16) | f->f_height;
 +      cfg |= src_pixfmt_map[i][1];
 +      writel(cfg, dev->regs + FLITE_REG_CISRCSIZE);
 +}
 +
 +/* Set the camera host input window offsets (cropping) */
 +void flite_hw_set_window_offset(struct fimc_lite *dev, struct flite_frame *f)
 +{
 +      u32 hoff2, voff2;
 +      u32 cfg;
 +
 +      cfg = readl(dev->regs + FLITE_REG_CIWDOFST);
 +      cfg &= ~FLITE_REG_CIWDOFST_OFST_MASK;
 +      cfg |= (f->rect.left << 16) | f->rect.top;
 +      cfg |= FLITE_REG_CIWDOFST_WINOFSEN;
 +      writel(cfg, dev->regs + FLITE_REG_CIWDOFST);
 +
 +      hoff2 = f->f_width - f->rect.width - f->rect.left;
 +      voff2 = f->f_height - f->rect.height - f->rect.top;
 +
 +      cfg = (hoff2 << 16) | voff2;
 +      writel(cfg, dev->regs + FLITE_REG_CIWDOFST2);
 +}
 +
 +/* Select camera port (A, B) */
 +static void flite_hw_set_camera_port(struct fimc_lite *dev, int id)
 +{
 +      u32 cfg = readl(dev->regs + FLITE_REG_CIGENERAL);
 +      if (id == 0)
 +              cfg &= ~FLITE_REG_CIGENERAL_CAM_B;
 +      else
 +              cfg |= FLITE_REG_CIGENERAL_CAM_B;
 +      writel(cfg, dev->regs + FLITE_REG_CIGENERAL);
 +}
 +
 +/* Select serial or parallel bus, camera port (A,B) and set signals polarity */
 +void flite_hw_set_camera_bus(struct fimc_lite *dev,
 +                           struct fimc_source_info *si)
 +{
 +      u32 cfg = readl(dev->regs + FLITE_REG_CIGCTRL);
 +      unsigned int flags = si->flags;
 +
 +      if (si->sensor_bus_type != FIMC_BUS_TYPE_MIPI_CSI2) {
 +              cfg &= ~(FLITE_REG_CIGCTRL_SELCAM_MIPI |
 +                       FLITE_REG_CIGCTRL_INVPOLPCLK |
 +                       FLITE_REG_CIGCTRL_INVPOLVSYNC |
 +                       FLITE_REG_CIGCTRL_INVPOLHREF);
 +
 +              if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING)
 +                      cfg |= FLITE_REG_CIGCTRL_INVPOLPCLK;
 +
 +              if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)
 +                      cfg |= FLITE_REG_CIGCTRL_INVPOLVSYNC;
 +
 +              if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)
 +                      cfg |= FLITE_REG_CIGCTRL_INVPOLHREF;
 +      } else {
 +              cfg |= FLITE_REG_CIGCTRL_SELCAM_MIPI;
 +      }
 +
 +      writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
 +
 +      flite_hw_set_camera_port(dev, si->mux_id);
 +}
 +
 +static void flite_hw_set_out_order(struct fimc_lite *dev, struct flite_frame *f)
 +{
 +      static const u32 pixcode[4][2] = {
 +              { V4L2_MBUS_FMT_YUYV8_2X8, FLITE_REG_CIODMAFMT_YCBYCR },
 +              { V4L2_MBUS_FMT_YVYU8_2X8, FLITE_REG_CIODMAFMT_YCRYCB },
 +              { V4L2_MBUS_FMT_UYVY8_2X8, FLITE_REG_CIODMAFMT_CBYCRY },
 +              { V4L2_MBUS_FMT_VYUY8_2X8, FLITE_REG_CIODMAFMT_CRYCBY },
 +      };
 +      u32 cfg = readl(dev->regs + FLITE_REG_CIODMAFMT);
-       while (i-- >= 0)
++      int i = ARRAY_SIZE(pixcode);
 +
++      while (--i >= 0)
 +              if (pixcode[i][0] == dev->fmt->mbus_code)
 +                      break;
 +      cfg &= ~FLITE_REG_CIODMAFMT_YCBCR_ORDER_MASK;
 +      writel(cfg | pixcode[i][1], dev->regs + FLITE_REG_CIODMAFMT);
 +}
 +
 +void flite_hw_set_dma_window(struct fimc_lite *dev, struct flite_frame *f)
 +{
 +      u32 cfg;
 +
 +      /* Maximum output pixel size */
 +      cfg = readl(dev->regs + FLITE_REG_CIOCAN);
 +      cfg &= ~FLITE_REG_CIOCAN_MASK;
 +      cfg = (f->f_height << 16) | f->f_width;
 +      writel(cfg, dev->regs + FLITE_REG_CIOCAN);
 +
 +      /* DMA offsets */
 +      cfg = readl(dev->regs + FLITE_REG_CIOOFF);
 +      cfg &= ~FLITE_REG_CIOOFF_MASK;
 +      cfg |= (f->rect.top << 16) | f->rect.left;
 +      writel(cfg, dev->regs + FLITE_REG_CIOOFF);
 +}
 +
 +/* Enable/disable output DMA, set output pixel size and offsets (composition) */
 +void flite_hw_set_output_dma(struct fimc_lite *dev, struct flite_frame *f,
 +                           bool enable)
 +{
 +      u32 cfg = readl(dev->regs + FLITE_REG_CIGCTRL);
 +
 +      if (!enable) {
 +              cfg |= FLITE_REG_CIGCTRL_ODMA_DISABLE;
 +              writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
 +              return;
 +      }
 +
 +      cfg &= ~FLITE_REG_CIGCTRL_ODMA_DISABLE;
 +      writel(cfg, dev->regs + FLITE_REG_CIGCTRL);
 +
 +      flite_hw_set_out_order(dev, f);
 +      flite_hw_set_dma_window(dev, f);
 +}
 +
 +void flite_hw_dump_regs(struct fimc_lite *dev, const char *label)
 +{
 +      struct {
 +              u32 offset;
 +              const char * const name;
 +      } registers[] = {
 +              { 0x00, "CISRCSIZE" },
 +              { 0x04, "CIGCTRL" },
 +              { 0x08, "CIIMGCPT" },
 +              { 0x0c, "CICPTSEQ" },
 +              { 0x10, "CIWDOFST" },
 +              { 0x14, "CIWDOFST2" },
 +              { 0x18, "CIODMAFMT" },
 +              { 0x20, "CIOCAN" },
 +              { 0x24, "CIOOFF" },
 +              { 0x30, "CIOSA" },
 +              { 0x40, "CISTATUS" },
 +              { 0x44, "CISTATUS2" },
 +              { 0xf0, "CITHOLD" },
 +              { 0xfc, "CIGENERAL" },
 +      };
 +      u32 i;
 +
 +      v4l2_info(&dev->subdev, "--- %s ---\n", label);
 +
 +      for (i = 0; i < ARRAY_SIZE(registers); i++) {
 +              u32 cfg = readl(dev->regs + registers[i].offset);
 +              v4l2_info(&dev->subdev, "%9s: 0x%08x\n",
 +                        registers[i].name, cfg);
 +      }
 +}
index bbcd743915604559af8d939cb7e68b03091e9984,0000000000000000000000000000000000000000..70c0cc2ad3e54be42f1a798b9712b3309512e3a1
mode 100644,000000..100644
--- /dev/null
@@@ -1,1626 -1,0 +1,1627 @@@
 +/*
 + * Samsung EXYNOS FIMC-LITE (camera host interface) driver
 +*
 + * Copyright (C) 2012 Samsung Electronics Co., Ltd.
 + * Sylwester Nawrocki <s.nawrocki@samsung.com>
 + *
 + * This program is free software; you can redistribute it and/or modify
 + * it under the terms of the GNU General Public License version 2 as
 + * published by the Free Software Foundation.
 + */
 +#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__
 +
 +#include <linux/bug.h>
 +#include <linux/device.h>
 +#include <linux/errno.h>
 +#include <linux/interrupt.h>
 +#include <linux/kernel.h>
 +#include <linux/list.h>
 +#include <linux/module.h>
 +#include <linux/of.h>
 +#include <linux/types.h>
 +#include <linux/platform_device.h>
 +#include <linux/pm_runtime.h>
 +#include <linux/slab.h>
 +#include <linux/videodev2.h>
 +
 +#include <media/v4l2-device.h>
 +#include <media/v4l2-ioctl.h>
 +#include <media/v4l2-mem2mem.h>
 +#include <media/videobuf2-core.h>
 +#include <media/videobuf2-dma-contig.h>
 +#include <media/s5p_fimc.h>
 +
 +#include "media-dev.h"
 +#include "fimc-lite.h"
 +#include "fimc-lite-reg.h"
 +
 +static int debug;
 +module_param(debug, int, 0644);
 +
 +static const struct fimc_fmt fimc_lite_formats[] = {
 +      {
 +              .name           = "YUV 4:2:2 packed, YCbYCr",
 +              .fourcc         = V4L2_PIX_FMT_YUYV,
 +              .depth          = { 16 },
 +              .color          = FIMC_FMT_YCBYCR422,
 +              .memplanes      = 1,
 +              .mbus_code      = V4L2_MBUS_FMT_YUYV8_2X8,
 +      }, {
 +              .name           = "YUV 4:2:2 packed, CbYCrY",
 +              .fourcc         = V4L2_PIX_FMT_UYVY,
 +              .depth          = { 16 },
 +              .color          = FIMC_FMT_CBYCRY422,
 +              .memplanes      = 1,
 +              .mbus_code      = V4L2_MBUS_FMT_UYVY8_2X8,
 +      }, {
 +              .name           = "YUV 4:2:2 packed, CrYCbY",
 +              .fourcc         = V4L2_PIX_FMT_VYUY,
 +              .depth          = { 16 },
 +              .color          = FIMC_FMT_CRYCBY422,
 +              .memplanes      = 1,
 +              .mbus_code      = V4L2_MBUS_FMT_VYUY8_2X8,
 +      }, {
 +              .name           = "YUV 4:2:2 packed, YCrYCb",
 +              .fourcc         = V4L2_PIX_FMT_YVYU,
 +              .depth          = { 16 },
 +              .color          = FIMC_FMT_YCRYCB422,
 +              .memplanes      = 1,
 +              .mbus_code      = V4L2_MBUS_FMT_YVYU8_2X8,
 +      }, {
 +              .name           = "RAW8 (GRBG)",
 +              .fourcc         = V4L2_PIX_FMT_SGRBG8,
 +              .depth          = { 8 },
 +              .color          = FIMC_FMT_RAW8,
 +              .memplanes      = 1,
 +              .mbus_code      = V4L2_MBUS_FMT_SGRBG8_1X8,
 +      }, {
 +              .name           = "RAW10 (GRBG)",
 +              .fourcc         = V4L2_PIX_FMT_SGRBG10,
 +              .depth          = { 10 },
 +              .color          = FIMC_FMT_RAW10,
 +              .memplanes      = 1,
 +              .mbus_code      = V4L2_MBUS_FMT_SGRBG10_1X10,
 +      }, {
 +              .name           = "RAW12 (GRBG)",
 +              .fourcc         = V4L2_PIX_FMT_SGRBG12,
 +              .depth          = { 12 },
 +              .color          = FIMC_FMT_RAW12,
 +              .memplanes      = 1,
 +              .mbus_code      = V4L2_MBUS_FMT_SGRBG12_1X12,
 +      },
 +};
 +
 +/**
 + * fimc_lite_find_format - lookup fimc color format by fourcc or media bus code
 + * @pixelformat: fourcc to match, ignored if null
 + * @mbus_code: media bus code to match, ignored if null
 + * @index: index to the fimc_lite_formats array, ignored if negative
 + */
 +static const struct fimc_fmt *fimc_lite_find_format(const u32 *pixelformat,
 +                                      const u32 *mbus_code, int index)
 +{
 +      const struct fimc_fmt *fmt, *def_fmt = NULL;
 +      unsigned int i;
 +      int id = 0;
 +
 +      if (index >= (int)ARRAY_SIZE(fimc_lite_formats))
 +              return NULL;
 +
 +      for (i = 0; i < ARRAY_SIZE(fimc_lite_formats); ++i) {
 +              fmt = &fimc_lite_formats[i];
 +              if (pixelformat && fmt->fourcc == *pixelformat)
 +                      return fmt;
 +              if (mbus_code && fmt->mbus_code == *mbus_code)
 +                      return fmt;
 +              if (index == id)
 +                      def_fmt = fmt;
 +              id++;
 +      }
 +      return def_fmt;
 +}
 +
 +static int fimc_lite_hw_init(struct fimc_lite *fimc, bool isp_output)
 +{
 +      struct fimc_pipeline *pipeline = &fimc->pipeline;
 +      struct v4l2_subdev *sensor;
 +      struct fimc_sensor_info *si;
 +      unsigned long flags;
 +
 +      sensor = isp_output ? fimc->sensor : pipeline->subdevs[IDX_SENSOR];
 +
 +      if (sensor == NULL)
 +              return -ENXIO;
 +
 +      if (fimc->fmt == NULL)
 +              return -EINVAL;
 +
 +      /* Get sensor configuration data from the sensor subdev */
 +      si = v4l2_get_subdev_hostdata(sensor);
 +      spin_lock_irqsave(&fimc->slock, flags);
 +
 +      flite_hw_set_camera_bus(fimc, &si->pdata);
 +      flite_hw_set_source_format(fimc, &fimc->inp_frame);
 +      flite_hw_set_window_offset(fimc, &fimc->inp_frame);
 +      flite_hw_set_output_dma(fimc, &fimc->out_frame, !isp_output);
 +      flite_hw_set_interrupt_mask(fimc);
 +      flite_hw_set_test_pattern(fimc, fimc->test_pattern->val);
 +
 +      if (debug > 0)
 +              flite_hw_dump_regs(fimc, __func__);
 +
 +      spin_unlock_irqrestore(&fimc->slock, flags);
 +      return 0;
 +}
 +
 +/*
 + * Reinitialize the driver so it is ready to start the streaming again.
 + * Set fimc->state to indicate stream off and the hardware shut down state.
 + * If not suspending (@suspend is false), return any buffers to videobuf2.
 + * Otherwise put any owned buffers onto the pending buffers queue, so they
 + * can be re-spun when the device is being resumed. Also perform FIMC
 + * software reset and disable streaming on the whole pipeline if required.
 + */
 +static int fimc_lite_reinit(struct fimc_lite *fimc, bool suspend)
 +{
 +      struct flite_buffer *buf;
 +      unsigned long flags;
 +      bool streaming;
 +
 +      spin_lock_irqsave(&fimc->slock, flags);
 +      streaming = fimc->state & (1 << ST_SENSOR_STREAM);
 +
 +      fimc->state &= ~(1 << ST_FLITE_RUN | 1 << ST_FLITE_OFF |
 +                       1 << ST_FLITE_STREAM | 1 << ST_SENSOR_STREAM);
 +      if (suspend)
 +              fimc->state |= (1 << ST_FLITE_SUSPENDED);
 +      else
 +              fimc->state &= ~(1 << ST_FLITE_PENDING |
 +                               1 << ST_FLITE_SUSPENDED);
 +
 +      /* Release unused buffers */
 +      while (!suspend && !list_empty(&fimc->pending_buf_q)) {
 +              buf = fimc_lite_pending_queue_pop(fimc);
 +              vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
 +      }
 +      /* If suspending put unused buffers onto pending queue */
 +      while (!list_empty(&fimc->active_buf_q)) {
 +              buf = fimc_lite_active_queue_pop(fimc);
 +              if (suspend)
 +                      fimc_lite_pending_queue_add(fimc, buf);
 +              else
 +                      vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
 +      }
 +
 +      spin_unlock_irqrestore(&fimc->slock, flags);
 +
 +      flite_hw_reset(fimc);
 +
 +      if (!streaming)
 +              return 0;
 +
 +      return fimc_pipeline_call(fimc, set_stream, &fimc->pipeline, 0);
 +}
 +
 +static int fimc_lite_stop_capture(struct fimc_lite *fimc, bool suspend)
 +{
 +      unsigned long flags;
 +
 +      if (!fimc_lite_active(fimc))
 +              return 0;
 +
 +      spin_lock_irqsave(&fimc->slock, flags);
 +      set_bit(ST_FLITE_OFF, &fimc->state);
 +      flite_hw_capture_stop(fimc);
 +      spin_unlock_irqrestore(&fimc->slock, flags);
 +
 +      wait_event_timeout(fimc->irq_queue,
 +                         !test_bit(ST_FLITE_OFF, &fimc->state),
 +                         (2*HZ/10)); /* 200 ms */
 +
 +      return fimc_lite_reinit(fimc, suspend);
 +}
 +
 +/* Must be called  with fimc.slock spinlock held. */
 +static void fimc_lite_config_update(struct fimc_lite *fimc)
 +{
 +      flite_hw_set_window_offset(fimc, &fimc->inp_frame);
 +      flite_hw_set_dma_window(fimc, &fimc->out_frame);
 +      flite_hw_set_test_pattern(fimc, fimc->test_pattern->val);
 +      clear_bit(ST_FLITE_CONFIG, &fimc->state);
 +}
 +
 +static irqreturn_t flite_irq_handler(int irq, void *priv)
 +{
 +      struct fimc_lite *fimc = priv;
 +      struct flite_buffer *vbuf;
 +      unsigned long flags;
 +      struct timeval *tv;
 +      struct timespec ts;
 +      u32 intsrc;
 +
 +      spin_lock_irqsave(&fimc->slock, flags);
 +
 +      intsrc = flite_hw_get_interrupt_source(fimc);
 +      flite_hw_clear_pending_irq(fimc);
 +
 +      if (test_and_clear_bit(ST_FLITE_OFF, &fimc->state)) {
 +              wake_up(&fimc->irq_queue);
 +              goto done;
 +      }
 +
 +      if (intsrc & FLITE_REG_CISTATUS_IRQ_SRC_OVERFLOW) {
 +              clear_bit(ST_FLITE_RUN, &fimc->state);
 +              fimc->events.data_overflow++;
 +      }
 +
 +      if (intsrc & FLITE_REG_CISTATUS_IRQ_SRC_LASTCAPEND) {
 +              flite_hw_clear_last_capture_end(fimc);
 +              clear_bit(ST_FLITE_STREAM, &fimc->state);
 +              wake_up(&fimc->irq_queue);
 +      }
 +
 +      if (atomic_read(&fimc->out_path) != FIMC_IO_DMA)
 +              goto done;
 +
 +      if ((intsrc & FLITE_REG_CISTATUS_IRQ_SRC_FRMSTART) &&
 +          test_bit(ST_FLITE_RUN, &fimc->state) &&
 +          !list_empty(&fimc->active_buf_q) &&
 +          !list_empty(&fimc->pending_buf_q)) {
 +              vbuf = fimc_lite_active_queue_pop(fimc);
 +              ktime_get_ts(&ts);
 +              tv = &vbuf->vb.v4l2_buf.timestamp;
 +              tv->tv_sec = ts.tv_sec;
 +              tv->tv_usec = ts.tv_nsec / NSEC_PER_USEC;
 +              vbuf->vb.v4l2_buf.sequence = fimc->frame_count++;
 +              vb2_buffer_done(&vbuf->vb, VB2_BUF_STATE_DONE);
 +
 +              vbuf = fimc_lite_pending_queue_pop(fimc);
 +              flite_hw_set_output_addr(fimc, vbuf->paddr);
 +              fimc_lite_active_queue_add(fimc, vbuf);
 +      }
 +
 +      if (test_bit(ST_FLITE_CONFIG, &fimc->state))
 +              fimc_lite_config_update(fimc);
 +
 +      if (list_empty(&fimc->pending_buf_q)) {
 +              flite_hw_capture_stop(fimc);
 +              clear_bit(ST_FLITE_STREAM, &fimc->state);
 +      }
 +done:
 +      set_bit(ST_FLITE_RUN, &fimc->state);
 +      spin_unlock_irqrestore(&fimc->slock, flags);
 +      return IRQ_HANDLED;
 +}
 +
 +static int start_streaming(struct vb2_queue *q, unsigned int count)
 +{
 +      struct fimc_lite *fimc = q->drv_priv;
 +      int ret;
 +
 +      fimc->frame_count = 0;
 +
 +      ret = fimc_lite_hw_init(fimc, false);
 +      if (ret) {
 +              fimc_lite_reinit(fimc, false);
 +              return ret;
 +      }
 +
 +      set_bit(ST_FLITE_PENDING, &fimc->state);
 +
 +      if (!list_empty(&fimc->active_buf_q) &&
 +          !test_and_set_bit(ST_FLITE_STREAM, &fimc->state)) {
 +              flite_hw_capture_start(fimc);
 +
 +              if (!test_and_set_bit(ST_SENSOR_STREAM, &fimc->state))
 +                      fimc_pipeline_call(fimc, set_stream,
 +                                         &fimc->pipeline, 1);
 +      }
 +      if (debug > 0)
 +              flite_hw_dump_regs(fimc, __func__);
 +
 +      return 0;
 +}
 +
 +static int stop_streaming(struct vb2_queue *q)
 +{
 +      struct fimc_lite *fimc = q->drv_priv;
 +
 +      if (!fimc_lite_active(fimc))
 +              return -EINVAL;
 +
 +      return fimc_lite_stop_capture(fimc, false);
 +}
 +
 +static int queue_setup(struct vb2_queue *vq, const struct v4l2_format *pfmt,
 +                     unsigned int *num_buffers, unsigned int *num_planes,
 +                     unsigned int sizes[], void *allocators[])
 +{
 +      const struct v4l2_pix_format_mplane *pixm = NULL;
 +      struct fimc_lite *fimc = vq->drv_priv;
 +      struct flite_frame *frame = &fimc->out_frame;
 +      const struct fimc_fmt *fmt = fimc->fmt;
 +      unsigned long wh;
 +      int i;
 +
 +      if (pfmt) {
 +              pixm = &pfmt->fmt.pix_mp;
 +              fmt = fimc_lite_find_format(&pixm->pixelformat, NULL, -1);
 +              wh = pixm->width * pixm->height;
 +      } else {
 +              wh = frame->f_width * frame->f_height;
 +      }
 +
 +      if (fmt == NULL)
 +              return -EINVAL;
 +
 +      *num_planes = fmt->memplanes;
 +
 +      for (i = 0; i < fmt->memplanes; i++) {
 +              unsigned int size = (wh * fmt->depth[i]) / 8;
 +              if (pixm)
 +                      sizes[i] = max(size, pixm->plane_fmt[i].sizeimage);
 +              else
 +                      sizes[i] = size;
 +              allocators[i] = fimc->alloc_ctx;
 +      }
 +
 +      return 0;
 +}
 +
 +static int buffer_prepare(struct vb2_buffer *vb)
 +{
 +      struct vb2_queue *vq = vb->vb2_queue;
 +      struct fimc_lite *fimc = vq->drv_priv;
 +      int i;
 +
 +      if (fimc->fmt == NULL)
 +              return -EINVAL;
 +
 +      for (i = 0; i < fimc->fmt->memplanes; i++) {
 +              unsigned long size = fimc->payload[i];
 +
 +              if (vb2_plane_size(vb, i) < size) {
 +                      v4l2_err(&fimc->vfd,
 +                               "User buffer too small (%ld < %ld)\n",
 +                               vb2_plane_size(vb, i), size);
 +                      return -EINVAL;
 +              }
 +              vb2_set_plane_payload(vb, i, size);
 +      }
 +
 +      return 0;
 +}
 +
 +static void buffer_queue(struct vb2_buffer *vb)
 +{
 +      struct flite_buffer *buf
 +              = container_of(vb, struct flite_buffer, vb);
 +      struct fimc_lite *fimc = vb2_get_drv_priv(vb->vb2_queue);
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&fimc->slock, flags);
 +      buf->paddr = vb2_dma_contig_plane_dma_addr(vb, 0);
 +
 +      if (!test_bit(ST_FLITE_SUSPENDED, &fimc->state) &&
 +          !test_bit(ST_FLITE_STREAM, &fimc->state) &&
 +          list_empty(&fimc->active_buf_q)) {
 +              flite_hw_set_output_addr(fimc, buf->paddr);
 +              fimc_lite_active_queue_add(fimc, buf);
 +      } else {
 +              fimc_lite_pending_queue_add(fimc, buf);
 +      }
 +
 +      if (vb2_is_streaming(&fimc->vb_queue) &&
 +          !list_empty(&fimc->pending_buf_q) &&
 +          !test_and_set_bit(ST_FLITE_STREAM, &fimc->state)) {
 +              flite_hw_capture_start(fimc);
 +              spin_unlock_irqrestore(&fimc->slock, flags);
 +
 +              if (!test_and_set_bit(ST_SENSOR_STREAM, &fimc->state))
 +                      fimc_pipeline_call(fimc, set_stream,
 +                                         &fimc->pipeline, 1);
 +              return;
 +      }
 +      spin_unlock_irqrestore(&fimc->slock, flags);
 +}
 +
 +static const struct vb2_ops fimc_lite_qops = {
 +      .queue_setup     = queue_setup,
 +      .buf_prepare     = buffer_prepare,
 +      .buf_queue       = buffer_queue,
 +      .wait_prepare    = vb2_ops_wait_prepare,
 +      .wait_finish     = vb2_ops_wait_finish,
 +      .start_streaming = start_streaming,
 +      .stop_streaming  = stop_streaming,
 +};
 +
 +static void fimc_lite_clear_event_counters(struct fimc_lite *fimc)
 +{
 +      unsigned long flags;
 +
 +      spin_lock_irqsave(&fimc->slock, flags);
 +      memset(&fimc->events, 0, sizeof(fimc->events));
 +      spin_unlock_irqrestore(&fimc->slock, flags);
 +}
 +
 +static int fimc_lite_open(struct file *file)
 +{
 +      struct fimc_lite *fimc = video_drvdata(file);
 +      struct media_entity *me = &fimc->vfd.entity;
 +      int ret;
 +
 +      mutex_lock(&me->parent->graph_mutex);
 +
 +      mutex_lock(&fimc->lock);
 +      if (atomic_read(&fimc->out_path) != FIMC_IO_DMA) {
 +              ret = -EBUSY;
 +              goto unlock;
 +      }
 +
 +      set_bit(ST_FLITE_IN_USE, &fimc->state);
 +      ret = pm_runtime_get_sync(&fimc->pdev->dev);
 +      if (ret < 0)
 +              goto unlock;
 +
 +      ret = v4l2_fh_open(file);
 +      if (ret < 0)
 +              goto err_pm;
 +
 +      if (!v4l2_fh_is_singular_file(file) ||
 +          atomic_read(&fimc->out_path) != FIMC_IO_DMA)
 +              goto unlock;
 +
 +      ret = fimc_pipeline_call(fimc, open, &fimc->pipeline,
 +                                              me, true);
 +      if (!ret) {
 +              fimc_lite_clear_event_counters(fimc);
 +              fimc->ref_count++;
 +              goto unlock;
 +      }
 +
 +      v4l2_fh_release(file);
 +err_pm:
 +      pm_runtime_put_sync(&fimc->pdev->dev);
 +      clear_bit(ST_FLITE_IN_USE, &fimc->state);
 +unlock:
 +      mutex_unlock(&fimc->lock);
 +      mutex_unlock(&me->parent->graph_mutex);
 +      return ret;
 +}
 +
 +static int fimc_lite_release(struct file *file)
 +{
 +      struct fimc_lite *fimc = video_drvdata(file);
 +
 +      mutex_lock(&fimc->lock);
 +
 +      if (v4l2_fh_is_singular_file(file) &&
 +          atomic_read(&fimc->out_path) == FIMC_IO_DMA) {
 +              clear_bit(ST_FLITE_IN_USE, &fimc->state);
 +              fimc_lite_stop_capture(fimc, false);
 +              fimc_pipeline_call(fimc, close, &fimc->pipeline);
 +              fimc->ref_count--;
 +      }
 +
 +      vb2_fop_release(file);
 +      pm_runtime_put(&fimc->pdev->dev);
 +      clear_bit(ST_FLITE_SUSPENDED, &fimc->state);
 +
 +      mutex_unlock(&fimc->lock);
 +      return 0;
 +}
 +
 +static const struct v4l2_file_operations fimc_lite_fops = {
 +      .owner          = THIS_MODULE,
 +      .open           = fimc_lite_open,
 +      .release        = fimc_lite_release,
 +      .poll           = vb2_fop_poll,
 +      .unlocked_ioctl = video_ioctl2,
 +      .mmap           = vb2_fop_mmap,
 +};
 +
 +/*
 + * Format and crop negotiation helpers
 + */
 +
 +static const struct fimc_fmt *fimc_lite_try_format(struct fimc_lite *fimc,
 +                                      u32 *width, u32 *height,
 +                                      u32 *code, u32 *fourcc, int pad)
 +{
 +      struct flite_variant *variant = fimc->variant;
 +      const struct fimc_fmt *fmt;
 +
 +      fmt = fimc_lite_find_format(fourcc, code, 0);
 +      if (WARN_ON(!fmt))
 +              return NULL;
 +
 +      if (code)
 +              *code = fmt->mbus_code;
 +      if (fourcc)
 +              *fourcc = fmt->fourcc;
 +
 +      if (pad == FLITE_SD_PAD_SINK) {
 +              v4l_bound_align_image(width, 8, variant->max_width,
 +                                    ffs(variant->out_width_align) - 1,
 +                                    height, 0, variant->max_height, 0, 0);
 +      } else {
 +              v4l_bound_align_image(width, 8, fimc->inp_frame.rect.width,
 +                                    ffs(variant->out_width_align) - 1,
 +                                    height, 0, fimc->inp_frame.rect.height,
 +                                    0, 0);
 +      }
 +
 +      v4l2_dbg(1, debug, &fimc->subdev, "code: 0x%x, %dx%d\n",
 +               code ? *code : 0, *width, *height);
 +
 +      return fmt;
 +}
 +
 +static void fimc_lite_try_crop(struct fimc_lite *fimc, struct v4l2_rect *r)
 +{
 +      struct flite_frame *frame = &fimc->inp_frame;
 +
 +      v4l_bound_align_image(&r->width, 0, frame->f_width, 0,
 +                            &r->height, 0, frame->f_height, 0, 0);
 +
 +      /* Adjust left/top if cropping rectangle got out of bounds */
 +      r->left = clamp_t(u32, r->left, 0, frame->f_width - r->width);
 +      r->left = round_down(r->left, fimc->variant->win_hor_offs_align);
 +      r->top  = clamp_t(u32, r->top, 0, frame->f_height - r->height);
 +
 +      v4l2_dbg(1, debug, &fimc->subdev, "(%d,%d)/%dx%d, sink fmt: %dx%d\n",
 +               r->left, r->top, r->width, r->height,
 +               frame->f_width, frame->f_height);
 +}
 +
 +static void fimc_lite_try_compose(struct fimc_lite *fimc, struct v4l2_rect *r)
 +{
 +      struct flite_frame *frame = &fimc->out_frame;
 +      struct v4l2_rect *crop_rect = &fimc->inp_frame.rect;
 +
 +      /* Scaling is not supported so we enforce compose rectangle size
 +         same as size of the sink crop rectangle. */
 +      r->width = crop_rect->width;
 +      r->height = crop_rect->height;
 +
 +      /* Adjust left/top if the composing rectangle got out of bounds */
 +      r->left = clamp_t(u32, r->left, 0, frame->f_width - r->width);
 +      r->left = round_down(r->left, fimc->variant->out_hor_offs_align);
 +      r->top  = clamp_t(u32, r->top, 0, fimc->out_frame.f_height - r->height);
 +
 +      v4l2_dbg(1, debug, &fimc->subdev, "(%d,%d)/%dx%d, source fmt: %dx%d\n",
 +               r->left, r->top, r->width, r->height,
 +               frame->f_width, frame->f_height);
 +}
 +
 +/*
 + * Video node ioctl operations
 + */
 +static int fimc_vidioc_querycap_capture(struct file *file, void *priv,
 +                                      struct v4l2_capability *cap)
 +{
 +      strlcpy(cap->driver, FIMC_LITE_DRV_NAME, sizeof(cap->driver));
 +      cap->bus_info[0] = 0;
 +      cap->card[0] = 0;
 +      cap->capabilities = V4L2_CAP_STREAMING;
 +      return 0;
 +}
 +
 +static int fimc_lite_enum_fmt_mplane(struct file *file, void *priv,
 +                                   struct v4l2_fmtdesc *f)
 +{
 +      const struct fimc_fmt *fmt;
 +
 +      if (f->index >= ARRAY_SIZE(fimc_lite_formats))
 +              return -EINVAL;
 +
 +      fmt = &fimc_lite_formats[f->index];
 +      strlcpy(f->description, fmt->name, sizeof(f->description));
 +      f->pixelformat = fmt->fourcc;
 +
 +      return 0;
 +}
 +
 +static int fimc_lite_g_fmt_mplane(struct file *file, void *fh,
 +                                struct v4l2_format *f)
 +{
 +      struct fimc_lite *fimc = video_drvdata(file);
 +      struct v4l2_pix_format_mplane *pixm = &f->fmt.pix_mp;
 +      struct v4l2_plane_pix_format *plane_fmt = &pixm->plane_fmt[0];
 +      struct flite_frame *frame = &fimc->out_frame;
 +      const struct fimc_fmt *fmt = fimc->fmt;
 +
 +      plane_fmt->bytesperline = (frame->f_width * fmt->depth[0]) / 8;
 +      plane_fmt->sizeimage = plane_fmt->bytesperline * frame->f_height;
 +
 +      pixm->num_planes = fmt->memplanes;
 +      pixm->pixelformat = fmt->fourcc;
 +      pixm->width = frame->f_width;
 +      pixm->height = frame->f_height;
 +      pixm->field = V4L2_FIELD_NONE;
 +      pixm->colorspace = V4L2_COLORSPACE_JPEG;
 +      return 0;
 +}
 +
 +static int fimc_lite_try_fmt(struct fimc_lite *fimc,
 +                           struct v4l2_pix_format_mplane *pixm,
 +                           const struct fimc_fmt **ffmt)
 +{
 +      struct flite_variant *variant = fimc->variant;
 +      u32 bpl = pixm->plane_fmt[0].bytesperline;
 +      const struct fimc_fmt *fmt;
 +
 +      fmt = fimc_lite_find_format(&pixm->pixelformat, NULL, 0);
 +      if (WARN_ON(fmt == NULL))
 +              return -EINVAL;
 +      if (ffmt)
 +              *ffmt = fmt;
 +      v4l_bound_align_image(&pixm->width, 8, variant->max_width,
 +                            ffs(variant->out_width_align) - 1,
 +                            &pixm->height, 0, variant->max_height, 0, 0);
 +
 +      if ((bpl == 0 || ((bpl * 8) / fmt->depth[0]) < pixm->width))
 +              pixm->plane_fmt[0].bytesperline = (pixm->width *
 +                                                 fmt->depth[0]) / 8;
 +
 +      if (pixm->plane_fmt[0].sizeimage == 0)
 +              pixm->plane_fmt[0].sizeimage = (pixm->width * pixm->height *
 +                                              fmt->depth[0]) / 8;
 +      pixm->num_planes = fmt->memplanes;
 +      pixm->pixelformat = fmt->fourcc;
 +      pixm->colorspace = V4L2_COLORSPACE_JPEG;
 +      pixm->field = V4L2_FIELD_NONE;
 +      return 0;
 +}
 +
 +static int fimc_lite_try_fmt_mplane(struct file *file, void *fh,
 +                                  struct v4l2_format *f)
 +{
 +      struct fimc_lite *fimc = video_drvdata(file);
 +      return fimc_lite_try_fmt(fimc, &f->fmt.pix_mp, NULL);
 +}
 +
 +static int fimc_lite_s_fmt_mplane(struct file *file, void *priv,
 +                                struct v4l2_format *f)
 +{
 +      struct v4l2_pix_format_mplane *pixm = &f->fmt.pix_mp;
 +      struct fimc_lite *fimc = video_drvdata(file);
 +      struct flite_frame *frame = &fimc->out_frame;
 +      const struct fimc_fmt *fmt = NULL;
 +      int ret;
 +
 +      if (vb2_is_busy(&fimc->vb_queue))
 +              return -EBUSY;
 +
 +      ret = fimc_lite_try_fmt(fimc, &f->fmt.pix_mp, &fmt);
 +      if (ret < 0)
 +              return ret;
 +
 +      fimc->fmt = fmt;
 +      fimc->payload[0] = max((pixm->width * pixm->height * fmt->depth[0]) / 8,
 +                             pixm->plane_fmt[0].sizeimage);
 +      frame->f_width = pixm->width;
 +      frame->f_height = pixm->height;
 +
 +      return 0;
 +}
 +
 +static int fimc_pipeline_validate(struct fimc_lite *fimc)
 +{
 +      struct v4l2_subdev *sd = &fimc->subdev;
 +      struct v4l2_subdev_format sink_fmt, src_fmt;
 +      struct media_pad *pad;
 +      int ret;
 +
 +      while (1) {
 +              /* Retrieve format at the sink pad */
 +              pad = &sd->entity.pads[0];
 +              if (!(pad->flags & MEDIA_PAD_FL_SINK))
 +                      break;
 +              /* Don't call FIMC subdev operation to avoid nested locking */
 +              if (sd == &fimc->subdev) {
 +                      struct flite_frame *ff = &fimc->out_frame;
 +                      sink_fmt.format.width = ff->f_width;
 +                      sink_fmt.format.height = ff->f_height;
 +                      sink_fmt.format.code = fimc->fmt->mbus_code;
 +              } else {
 +                      sink_fmt.pad = pad->index;
 +                      sink_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
 +                      ret = v4l2_subdev_call(sd, pad, get_fmt, NULL,
 +                                             &sink_fmt);
 +                      if (ret < 0 && ret != -ENOIOCTLCMD)
 +                              return -EPIPE;
 +              }
 +              /* Retrieve format at the source pad */
 +              pad = media_entity_remote_source(pad);
 +              if (pad == NULL ||
 +                  media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV)
 +                      break;
 +
 +              sd = media_entity_to_v4l2_subdev(pad->entity);
 +              src_fmt.pad = pad->index;
 +              src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
 +              ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &src_fmt);
 +              if (ret < 0 && ret != -ENOIOCTLCMD)
 +                      return -EPIPE;
 +
 +              if (src_fmt.format.width != sink_fmt.format.width ||
 +                  src_fmt.format.height != sink_fmt.format.height ||
 +                  src_fmt.format.code != sink_fmt.format.code)
 +                      return -EPIPE;
 +      }
 +      return 0;
 +}
 +
 +static int fimc_lite_streamon(struct file *file, void *priv,
 +                            enum v4l2_buf_type type)
 +{
 +      struct fimc_lite *fimc = video_drvdata(file);
 +      struct media_entity *entity = &fimc->vfd.entity;
 +      struct fimc_pipeline *p = &fimc->pipeline;
 +      int ret;
 +
 +      if (fimc_lite_active(fimc))
 +              return -EBUSY;
 +
 +      ret = media_entity_pipeline_start(entity, p->m_pipeline);
 +      if (ret < 0)
 +              return ret;
 +
 +      ret = fimc_pipeline_validate(fimc);
 +      if (ret < 0)
 +              goto err_p_stop;
 +
 +      ret = vb2_ioctl_streamon(file, priv, type);
 +      if (!ret)
 +              return ret;
 +err_p_stop:
 +      media_entity_pipeline_stop(entity);
 +      return 0;
 +}
 +
 +static int fimc_lite_streamoff(struct file *file, void *priv,
 +                             enum v4l2_buf_type type)
 +{
 +      struct fimc_lite *fimc = video_drvdata(file);
 +      int ret;
 +
 +      ret = vb2_ioctl_streamoff(file, priv, type);
 +      if (ret == 0)
 +              media_entity_pipeline_stop(&fimc->vfd.entity);
 +      return ret;
 +}
 +
 +static int fimc_lite_reqbufs(struct file *file, void *priv,
 +                           struct v4l2_requestbuffers *reqbufs)
 +{
 +      struct fimc_lite *fimc = video_drvdata(file);
 +      int ret;
 +
 +      reqbufs->count = max_t(u32, FLITE_REQ_BUFS_MIN, reqbufs->count);
 +      ret = vb2_ioctl_reqbufs(file, priv, reqbufs);
 +      if (!ret)
 +              fimc->reqbufs_count = reqbufs->count;
 +
 +      return ret;
 +}
 +
 +/* Return 1 if rectangle a is enclosed in rectangle b, or 0 otherwise. */
 +static int enclosed_rectangle(struct v4l2_rect *a, struct v4l2_rect *b)
 +{
 +      if (a->left < b->left || a->top < b->top)
 +              return 0;
 +      if (a->left + a->width > b->left + b->width)
 +              return 0;
 +      if (a->top + a->height > b->top + b->height)
 +              return 0;
 +
 +      return 1;
 +}
 +
 +static int fimc_lite_g_selection(struct file *file, void *fh,
 +                               struct v4l2_selection *sel)
 +{
 +      struct fimc_lite *fimc = video_drvdata(file);
 +      struct flite_frame *f = &fimc->out_frame;
 +
 +      if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
 +              return -EINVAL;
 +
 +      switch (sel->target) {
 +      case V4L2_SEL_TGT_COMPOSE_BOUNDS:
 +      case V4L2_SEL_TGT_COMPOSE_DEFAULT:
 +              sel->r.left = 0;
 +              sel->r.top = 0;
 +              sel->r.width = f->f_width;
 +              sel->r.height = f->f_height;
 +              return 0;
 +
 +      case V4L2_SEL_TGT_COMPOSE:
 +              sel->r = f->rect;
 +              return 0;
 +      }
 +
 +      return -EINVAL;
 +}
 +
 +static int fimc_lite_s_selection(struct file *file, void *fh,
 +                               struct v4l2_selection *sel)
 +{
 +      struct fimc_lite *fimc = video_drvdata(file);
 +      struct flite_frame *f = &fimc->out_frame;
 +      struct v4l2_rect rect = sel->r;
 +      unsigned long flags;
 +
 +      if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE ||
 +          sel->target != V4L2_SEL_TGT_COMPOSE)
 +              return -EINVAL;
 +
 +      fimc_lite_try_compose(fimc, &rect);
 +
 +      if ((sel->flags & V4L2_SEL_FLAG_LE) &&
 +          !enclosed_rectangle(&rect, &sel->r))
 +              return -ERANGE;
 +
 +      if ((sel->flags & V4L2_SEL_FLAG_GE) &&
 +          !enclosed_rectangle(&sel->r, &rect))
 +              return -ERANGE;
 +
 +      sel->r = rect;
 +      spin_lock_irqsave(&fimc->slock, flags);
 +      f->rect = rect;
 +      set_bit(ST_FLITE_CONFIG, &fimc->state);
 +      spin_unlock_irqrestore(&fimc->slock, flags);
 +
 +      return 0;
 +}
 +
 +static const struct v4l2_ioctl_ops fimc_lite_ioctl_ops = {
 +      .vidioc_querycap                = fimc_vidioc_querycap_capture,
 +      .vidioc_enum_fmt_vid_cap_mplane = fimc_lite_enum_fmt_mplane,
 +      .vidioc_try_fmt_vid_cap_mplane  = fimc_lite_try_fmt_mplane,
 +      .vidioc_s_fmt_vid_cap_mplane    = fimc_lite_s_fmt_mplane,
 +      .vidioc_g_fmt_vid_cap_mplane    = fimc_lite_g_fmt_mplane,
 +      .vidioc_g_selection             = fimc_lite_g_selection,
 +      .vidioc_s_selection             = fimc_lite_s_selection,
 +      .vidioc_reqbufs                 = fimc_lite_reqbufs,
 +      .vidioc_querybuf                = vb2_ioctl_querybuf,
 +      .vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
 +      .vidioc_create_bufs             = vb2_ioctl_create_bufs,
 +      .vidioc_qbuf                    = vb2_ioctl_qbuf,
 +      .vidioc_dqbuf                   = vb2_ioctl_dqbuf,
 +      .vidioc_streamon                = fimc_lite_streamon,
 +      .vidioc_streamoff               = fimc_lite_streamoff,
 +};
 +
 +/* Called with the media graph mutex held */
 +static struct v4l2_subdev *__find_remote_sensor(struct media_entity *me)
 +{
 +      struct media_pad *pad = &me->pads[0];
 +      struct v4l2_subdev *sd;
 +
 +      while (pad->flags & MEDIA_PAD_FL_SINK) {
 +              /* source pad */
 +              pad = media_entity_remote_source(pad);
 +              if (pad == NULL ||
 +                  media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV)
 +                      break;
 +
 +              sd = media_entity_to_v4l2_subdev(pad->entity);
 +
 +              if (sd->grp_id == GRP_ID_FIMC_IS_SENSOR)
 +                      return sd;
 +              /* sink pad */
 +              pad = &sd->entity.pads[0];
 +      }
 +      return NULL;
 +}
 +
 +/* Capture subdev media entity operations */
 +static int fimc_lite_link_setup(struct media_entity *entity,
 +                              const struct media_pad *local,
 +                              const struct media_pad *remote, u32 flags)
 +{
 +      struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
 +      struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
 +      unsigned int remote_ent_type = media_entity_type(remote->entity);
 +      int ret = 0;
 +
 +      if (WARN_ON(fimc == NULL))
 +              return 0;
 +
 +      v4l2_dbg(1, debug, sd, "%s: %s --> %s, flags: 0x%x. source_id: 0x%x\n",
 +               __func__, remote->entity->name, local->entity->name,
 +               flags, fimc->source_subdev_grp_id);
 +
 +      mutex_lock(&fimc->lock);
 +
 +      switch (local->index) {
 +      case FLITE_SD_PAD_SINK:
 +              if (remote_ent_type != MEDIA_ENT_T_V4L2_SUBDEV) {
 +                      ret = -EINVAL;
 +                      break;
 +              }
 +              if (flags & MEDIA_LNK_FL_ENABLED) {
 +                      if (fimc->source_subdev_grp_id == 0)
 +                              fimc->source_subdev_grp_id = sd->grp_id;
 +                      else
 +                              ret = -EBUSY;
 +              } else {
 +                      fimc->source_subdev_grp_id = 0;
 +                      fimc->sensor = NULL;
 +              }
 +              break;
 +
 +      case FLITE_SD_PAD_SOURCE_DMA:
 +              if (!(flags & MEDIA_LNK_FL_ENABLED))
 +                      atomic_set(&fimc->out_path, FIMC_IO_NONE);
 +              else if (remote_ent_type == MEDIA_ENT_T_DEVNODE)
 +                      atomic_set(&fimc->out_path, FIMC_IO_DMA);
 +              else
 +                      ret = -EINVAL;
 +              break;
 +
 +      case FLITE_SD_PAD_SOURCE_ISP:
 +              if (!(flags & MEDIA_LNK_FL_ENABLED))
 +                      atomic_set(&fimc->out_path, FIMC_IO_NONE);
 +              else if (remote_ent_type == MEDIA_ENT_T_V4L2_SUBDEV)
 +                      atomic_set(&fimc->out_path, FIMC_IO_ISP);
 +              else
 +                      ret = -EINVAL;
 +              break;
 +
 +      default:
 +              v4l2_err(sd, "Invalid pad index\n");
 +              ret = -EINVAL;
 +      }
 +      mb();
 +
 +      mutex_unlock(&fimc->lock);
 +      return ret;
 +}
 +
 +static const struct media_entity_operations fimc_lite_subdev_media_ops = {
 +      .link_setup = fimc_lite_link_setup,
 +};
 +
 +static int fimc_lite_subdev_enum_mbus_code(struct v4l2_subdev *sd,
 +                                         struct v4l2_subdev_fh *fh,
 +                                         struct v4l2_subdev_mbus_code_enum *code)
 +{
 +      const struct fimc_fmt *fmt;
 +
 +      fmt = fimc_lite_find_format(NULL, NULL, code->index);
 +      if (!fmt)
 +              return -EINVAL;
 +      code->code = fmt->mbus_code;
 +      return 0;
 +}
 +
 +static int fimc_lite_subdev_get_fmt(struct v4l2_subdev *sd,
 +                                  struct v4l2_subdev_fh *fh,
 +                                  struct v4l2_subdev_format *fmt)
 +{
 +      struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
 +      struct v4l2_mbus_framefmt *mf = &fmt->format;
 +      struct flite_frame *f = &fimc->out_frame;
 +
 +      if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
 +              mf = v4l2_subdev_get_try_format(fh, fmt->pad);
 +              fmt->format = *mf;
 +              return 0;
 +      }
 +      mf->colorspace = V4L2_COLORSPACE_JPEG;
 +
 +      mutex_lock(&fimc->lock);
 +      mf->code = fimc->fmt->mbus_code;
 +
 +      if (fmt->pad == FLITE_SD_PAD_SINK) {
 +              /* full camera input frame size */
 +              mf->width = f->f_width;
 +              mf->height = f->f_height;
 +      } else {
 +              /* crop size */
 +              mf->width = f->rect.width;
 +              mf->height = f->rect.height;
 +      }
 +      mutex_unlock(&fimc->lock);
 +      return 0;
 +}
 +
 +static int fimc_lite_subdev_set_fmt(struct v4l2_subdev *sd,
 +                                  struct v4l2_subdev_fh *fh,
 +                                  struct v4l2_subdev_format *fmt)
 +{
 +      struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
 +      struct v4l2_mbus_framefmt *mf = &fmt->format;
 +      struct flite_frame *sink = &fimc->inp_frame;
 +      struct flite_frame *source = &fimc->out_frame;
 +      const struct fimc_fmt *ffmt;
 +
 +      v4l2_dbg(1, debug, sd, "pad%d: code: 0x%x, %dx%d\n",
 +               fmt->pad, mf->code, mf->width, mf->height);
 +
 +      mf->colorspace = V4L2_COLORSPACE_JPEG;
 +      mutex_lock(&fimc->lock);
 +
 +      if ((atomic_read(&fimc->out_path) == FIMC_IO_ISP &&
 +          sd->entity.stream_count > 0) ||
 +          (atomic_read(&fimc->out_path) == FIMC_IO_DMA &&
 +          vb2_is_busy(&fimc->vb_queue))) {
 +              mutex_unlock(&fimc->lock);
 +              return -EBUSY;
 +      }
 +
 +      ffmt = fimc_lite_try_format(fimc, &mf->width, &mf->height,
 +                                  &mf->code, NULL, fmt->pad);
 +
 +      if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
 +              mf = v4l2_subdev_get_try_format(fh, fmt->pad);
 +              *mf = fmt->format;
 +              mutex_unlock(&fimc->lock);
 +              return 0;
 +      }
 +
 +      if (fmt->pad == FLITE_SD_PAD_SINK) {
 +              sink->f_width = mf->width;
 +              sink->f_height = mf->height;
 +              fimc->fmt = ffmt;
 +              /* Set sink crop rectangle */
 +              sink->rect.width = mf->width;
 +              sink->rect.height = mf->height;
 +              sink->rect.left = 0;
 +              sink->rect.top = 0;
 +              /* Reset source format and crop rectangle */
 +              source->rect = sink->rect;
 +              source->f_width = mf->width;
 +              source->f_height = mf->height;
 +      } else {
 +              /* Allow changing format only on sink pad */
 +              mf->code = fimc->fmt->mbus_code;
 +              mf->width = sink->rect.width;
 +              mf->height = sink->rect.height;
 +      }
 +
 +      mutex_unlock(&fimc->lock);
 +      return 0;
 +}
 +
 +static int fimc_lite_subdev_get_selection(struct v4l2_subdev *sd,
 +                                        struct v4l2_subdev_fh *fh,
 +                                        struct v4l2_subdev_selection *sel)
 +{
 +      struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
 +      struct flite_frame *f = &fimc->inp_frame;
 +
 +      if ((sel->target != V4L2_SEL_TGT_CROP &&
 +           sel->target != V4L2_SEL_TGT_CROP_BOUNDS) ||
 +           sel->pad != FLITE_SD_PAD_SINK)
 +              return -EINVAL;
 +
 +      if (sel->which == V4L2_SUBDEV_FORMAT_TRY) {
 +              sel->r = *v4l2_subdev_get_try_crop(fh, sel->pad);
 +              return 0;
 +      }
 +
 +      mutex_lock(&fimc->lock);
 +      if (sel->target == V4L2_SEL_TGT_CROP) {
 +              sel->r = f->rect;
 +      } else {
 +              sel->r.left = 0;
 +              sel->r.top = 0;
 +              sel->r.width = f->f_width;
 +              sel->r.height = f->f_height;
 +      }
 +      mutex_unlock(&fimc->lock);
 +
 +      v4l2_dbg(1, debug, sd, "%s: (%d,%d) %dx%d, f_w: %d, f_h: %d\n",
 +               __func__, f->rect.left, f->rect.top, f->rect.width,
 +               f->rect.height, f->f_width, f->f_height);
 +
 +      return 0;
 +}
 +
 +static int fimc_lite_subdev_set_selection(struct v4l2_subdev *sd,
 +                                        struct v4l2_subdev_fh *fh,
 +                                        struct v4l2_subdev_selection *sel)
 +{
 +      struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
 +      struct flite_frame *f = &fimc->inp_frame;
 +      int ret = 0;
 +
 +      if (sel->target != V4L2_SEL_TGT_CROP || sel->pad != FLITE_SD_PAD_SINK)
 +              return -EINVAL;
 +
 +      mutex_lock(&fimc->lock);
 +      fimc_lite_try_crop(fimc, &sel->r);
 +
 +      if (sel->which == V4L2_SUBDEV_FORMAT_TRY) {
 +              *v4l2_subdev_get_try_crop(fh, sel->pad) = sel->r;
 +      } else {
 +              unsigned long flags;
 +              spin_lock_irqsave(&fimc->slock, flags);
 +              f->rect = sel->r;
 +              /* Same crop rectangle on the source pad */
 +              fimc->out_frame.rect = sel->r;
 +              set_bit(ST_FLITE_CONFIG, &fimc->state);
 +              spin_unlock_irqrestore(&fimc->slock, flags);
 +      }
 +      mutex_unlock(&fimc->lock);
 +
 +      v4l2_dbg(1, debug, sd, "%s: (%d,%d) %dx%d, f_w: %d, f_h: %d\n",
 +               __func__, f->rect.left, f->rect.top, f->rect.width,
 +               f->rect.height, f->f_width, f->f_height);
 +
 +      return ret;
 +}
 +
 +static int fimc_lite_subdev_s_stream(struct v4l2_subdev *sd, int on)
 +{
 +      struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
 +      unsigned long flags;
 +      int ret;
 +
 +      /*
 +       * Find sensor subdev linked to FIMC-LITE directly or through
 +       * MIPI-CSIS. This is required for configuration where FIMC-LITE
 +       * is used as a subdev only and feeds data internally to FIMC-IS.
 +       * The pipeline links are protected through entity.stream_count
 +       * so there is no need to take the media graph mutex here.
 +       */
 +      fimc->sensor = __find_remote_sensor(&sd->entity);
 +
 +      if (atomic_read(&fimc->out_path) != FIMC_IO_ISP)
 +              return -ENOIOCTLCMD;
 +
 +      mutex_lock(&fimc->lock);
 +      if (on) {
 +              flite_hw_reset(fimc);
 +              ret = fimc_lite_hw_init(fimc, true);
 +              if (!ret) {
 +                      spin_lock_irqsave(&fimc->slock, flags);
 +                      flite_hw_capture_start(fimc);
 +                      spin_unlock_irqrestore(&fimc->slock, flags);
 +              }
 +      } else {
 +              set_bit(ST_FLITE_OFF, &fimc->state);
 +
 +              spin_lock_irqsave(&fimc->slock, flags);
 +              flite_hw_capture_stop(fimc);
 +              spin_unlock_irqrestore(&fimc->slock, flags);
 +
 +              ret = wait_event_timeout(fimc->irq_queue,
 +                              !test_bit(ST_FLITE_OFF, &fimc->state),
 +                              msecs_to_jiffies(200));
 +              if (ret == 0)
 +                      v4l2_err(sd, "s_stream(0) timeout\n");
 +              clear_bit(ST_FLITE_RUN, &fimc->state);
 +      }
 +
 +      mutex_unlock(&fimc->lock);
 +      return ret;
 +}
 +
 +static int fimc_lite_log_status(struct v4l2_subdev *sd)
 +{
 +      struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
 +
 +      flite_hw_dump_regs(fimc, __func__);
 +      return 0;
 +}
 +
 +static int fimc_lite_subdev_registered(struct v4l2_subdev *sd)
 +{
 +      struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
 +      struct vb2_queue *q = &fimc->vb_queue;
 +      struct video_device *vfd = &fimc->vfd;
 +      int ret;
 +
 +      memset(vfd, 0, sizeof(*vfd));
 +
 +      fimc->fmt = &fimc_lite_formats[0];
 +      atomic_set(&fimc->out_path, FIMC_IO_DMA);
 +
 +      snprintf(vfd->name, sizeof(vfd->name), "fimc-lite.%d.capture",
 +               fimc->index);
 +
 +      vfd->fops = &fimc_lite_fops;
 +      vfd->ioctl_ops = &fimc_lite_ioctl_ops;
 +      vfd->v4l2_dev = sd->v4l2_dev;
 +      vfd->minor = -1;
 +      vfd->release = video_device_release_empty;
 +      vfd->queue = q;
 +      fimc->reqbufs_count = 0;
 +
 +      INIT_LIST_HEAD(&fimc->pending_buf_q);
 +      INIT_LIST_HEAD(&fimc->active_buf_q);
 +
 +      memset(q, 0, sizeof(*q));
 +      q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
 +      q->io_modes = VB2_MMAP | VB2_USERPTR;
 +      q->ops = &fimc_lite_qops;
 +      q->mem_ops = &vb2_dma_contig_memops;
 +      q->buf_struct_size = sizeof(struct flite_buffer);
 +      q->drv_priv = fimc;
 +      q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
 +      q->lock = &fimc->lock;
 +
 +      ret = vb2_queue_init(q);
 +      if (ret < 0)
 +              return ret;
 +
 +      fimc->vd_pad.flags = MEDIA_PAD_FL_SINK;
 +      ret = media_entity_init(&vfd->entity, 1, &fimc->vd_pad, 0);
 +      if (ret < 0)
 +              return ret;
 +
 +      video_set_drvdata(vfd, fimc);
 +      fimc->pipeline_ops = v4l2_get_subdev_hostdata(sd);
 +
 +      ret = video_register_device(vfd, VFL_TYPE_GRABBER, -1);
 +      if (ret < 0) {
 +              media_entity_cleanup(&vfd->entity);
 +              fimc->pipeline_ops = NULL;
 +              return ret;
 +      }
 +
 +      v4l2_info(sd->v4l2_dev, "Registered %s as /dev/%s\n",
 +                vfd->name, video_device_node_name(vfd));
 +      return 0;
 +}
 +
 +static void fimc_lite_subdev_unregistered(struct v4l2_subdev *sd)
 +{
 +      struct fimc_lite *fimc = v4l2_get_subdevdata(sd);
 +
 +      if (fimc == NULL)
 +              return;
 +
 +      if (video_is_registered(&fimc->vfd)) {
 +              video_unregister_device(&fimc->vfd);
 +              media_entity_cleanup(&fimc->vfd.entity);
 +              fimc->pipeline_ops = NULL;
 +      }
 +}
 +
 +static const struct v4l2_subdev_internal_ops fimc_lite_subdev_internal_ops = {
 +      .registered = fimc_lite_subdev_registered,
 +      .unregistered = fimc_lite_subdev_unregistered,
 +};
 +
 +static const struct v4l2_subdev_pad_ops fimc_lite_subdev_pad_ops = {
 +      .enum_mbus_code = fimc_lite_subdev_enum_mbus_code,
 +      .get_selection = fimc_lite_subdev_get_selection,
 +      .set_selection = fimc_lite_subdev_set_selection,
 +      .get_fmt = fimc_lite_subdev_get_fmt,
 +      .set_fmt = fimc_lite_subdev_set_fmt,
 +};
 +
 +static const struct v4l2_subdev_video_ops fimc_lite_subdev_video_ops = {
 +      .s_stream = fimc_lite_subdev_s_stream,
 +};
 +
 +static const struct v4l2_subdev_core_ops fimc_lite_core_ops = {
 +      .log_status = fimc_lite_log_status,
 +};
 +
 +static struct v4l2_subdev_ops fimc_lite_subdev_ops = {
 +      .core = &fimc_lite_core_ops,
 +      .video = &fimc_lite_subdev_video_ops,
 +      .pad = &fimc_lite_subdev_pad_ops,
 +};
 +
 +static int fimc_lite_s_ctrl(struct v4l2_ctrl *ctrl)
 +{
 +      struct fimc_lite *fimc = container_of(ctrl->handler, struct fimc_lite,
 +                                            ctrl_handler);
 +      set_bit(ST_FLITE_CONFIG, &fimc->state);
 +      return 0;
 +}
 +
 +static const struct v4l2_ctrl_ops fimc_lite_ctrl_ops = {
 +      .s_ctrl = fimc_lite_s_ctrl,
 +};
 +
 +static const struct v4l2_ctrl_config fimc_lite_ctrl = {
 +      .ops    = &fimc_lite_ctrl_ops,
 +      .id     = V4L2_CTRL_CLASS_USER | 0x1001,
 +      .type   = V4L2_CTRL_TYPE_BOOLEAN,
 +      .name   = "Test Pattern 640x480",
++      .step   = 1,
 +};
 +
 +static int fimc_lite_create_capture_subdev(struct fimc_lite *fimc)
 +{
 +      struct v4l2_ctrl_handler *handler = &fimc->ctrl_handler;
 +      struct v4l2_subdev *sd = &fimc->subdev;
 +      int ret;
 +
 +      v4l2_subdev_init(sd, &fimc_lite_subdev_ops);
 +      sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
 +      snprintf(sd->name, sizeof(sd->name), "FIMC-LITE.%d", fimc->index);
 +
 +      fimc->subdev_pads[FLITE_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
 +      fimc->subdev_pads[FLITE_SD_PAD_SOURCE_DMA].flags = MEDIA_PAD_FL_SOURCE;
 +      fimc->subdev_pads[FLITE_SD_PAD_SOURCE_ISP].flags = MEDIA_PAD_FL_SOURCE;
 +      ret = media_entity_init(&sd->entity, FLITE_SD_PADS_NUM,
 +                              fimc->subdev_pads, 0);
 +      if (ret)
 +              return ret;
 +
 +      v4l2_ctrl_handler_init(handler, 1);
 +      fimc->test_pattern = v4l2_ctrl_new_custom(handler, &fimc_lite_ctrl,
 +                                                NULL);
 +      if (handler->error) {
 +              media_entity_cleanup(&sd->entity);
 +              return handler->error;
 +      }
 +
 +      sd->ctrl_handler = handler;
 +      sd->internal_ops = &fimc_lite_subdev_internal_ops;
 +      sd->entity.ops = &fimc_lite_subdev_media_ops;
 +      v4l2_set_subdevdata(sd, fimc);
 +
 +      return 0;
 +}
 +
 +static void fimc_lite_unregister_capture_subdev(struct fimc_lite *fimc)
 +{
 +      struct v4l2_subdev *sd = &fimc->subdev;
 +
 +      v4l2_device_unregister_subdev(sd);
 +      media_entity_cleanup(&sd->entity);
 +      v4l2_ctrl_handler_free(&fimc->ctrl_handler);
 +      v4l2_set_subdevdata(sd, NULL);
 +}
 +
 +static void fimc_lite_clk_put(struct fimc_lite *fimc)
 +{
 +      if (IS_ERR_OR_NULL(fimc->clock))
 +              return;
 +
 +      clk_unprepare(fimc->clock);
 +      clk_put(fimc->clock);
 +      fimc->clock = NULL;
 +}
 +
 +static int fimc_lite_clk_get(struct fimc_lite *fimc)
 +{
 +      int ret;
 +
 +      fimc->clock = clk_get(&fimc->pdev->dev, FLITE_CLK_NAME);
 +      if (IS_ERR(fimc->clock))
 +              return PTR_ERR(fimc->clock);
 +
 +      ret = clk_prepare(fimc->clock);
 +      if (ret < 0) {
 +              clk_put(fimc->clock);
 +              fimc->clock = NULL;
 +      }
 +      return ret;
 +}
 +
 +static const struct of_device_id flite_of_match[];
 +
 +static int fimc_lite_probe(struct platform_device *pdev)
 +{
 +      struct flite_drvdata *drv_data = NULL;
 +      struct device *dev = &pdev->dev;
 +      const struct of_device_id *of_id;
 +      struct fimc_lite *fimc;
 +      struct resource *res;
 +      int ret;
 +
 +      fimc = devm_kzalloc(dev, sizeof(*fimc), GFP_KERNEL);
 +      if (!fimc)
 +              return -ENOMEM;
 +
 +      if (dev->of_node) {
 +              of_id = of_match_node(flite_of_match, dev->of_node);
 +              if (of_id)
 +                      drv_data = (struct flite_drvdata *)of_id->data;
 +              fimc->index = of_alias_get_id(dev->of_node, "fimc-lite");
 +      } else {
 +              drv_data = fimc_lite_get_drvdata(pdev);
 +              fimc->index = pdev->id;
 +      }
 +
 +      if (!drv_data || fimc->index < 0 || fimc->index >= FIMC_LITE_MAX_DEVS)
 +              return -EINVAL;
 +
 +      fimc->variant = drv_data->variant[fimc->index];
 +      fimc->pdev = pdev;
 +
 +      init_waitqueue_head(&fimc->irq_queue);
 +      spin_lock_init(&fimc->slock);
 +      mutex_init(&fimc->lock);
 +
 +      res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 +      fimc->regs = devm_ioremap_resource(dev, res);
 +      if (IS_ERR(fimc->regs))
 +              return PTR_ERR(fimc->regs);
 +
 +      res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
 +      if (res == NULL) {
 +              dev_err(dev, "Failed to get IRQ resource\n");
 +              return -ENXIO;
 +      }
 +
 +      ret = fimc_lite_clk_get(fimc);
 +      if (ret)
 +              return ret;
 +
 +      ret = devm_request_irq(dev, res->start, flite_irq_handler,
 +                             0, dev_name(dev), fimc);
 +      if (ret) {
 +              dev_err(dev, "Failed to install irq (%d)\n", ret);
 +              goto err_clk;
 +      }
 +
 +      /* The video node will be created within the subdev's registered() op */
 +      ret = fimc_lite_create_capture_subdev(fimc);
 +      if (ret)
 +              goto err_clk;
 +
 +      platform_set_drvdata(pdev, fimc);
 +      pm_runtime_enable(dev);
 +      ret = pm_runtime_get_sync(dev);
 +      if (ret < 0)
 +              goto err_sd;
 +
 +      fimc->alloc_ctx = vb2_dma_contig_init_ctx(dev);
 +      if (IS_ERR(fimc->alloc_ctx)) {
 +              ret = PTR_ERR(fimc->alloc_ctx);
 +              goto err_pm;
 +      }
 +      pm_runtime_put(dev);
 +
 +      dev_dbg(dev, "FIMC-LITE.%d registered successfully\n",
 +              fimc->index);
 +      return 0;
 +err_pm:
 +      pm_runtime_put(dev);
 +err_sd:
 +      fimc_lite_unregister_capture_subdev(fimc);
 +err_clk:
 +      fimc_lite_clk_put(fimc);
 +      return ret;
 +}
 +
 +static int fimc_lite_runtime_resume(struct device *dev)
 +{
 +      struct fimc_lite *fimc = dev_get_drvdata(dev);
 +
 +      clk_enable(fimc->clock);
 +      return 0;
 +}
 +
 +static int fimc_lite_runtime_suspend(struct device *dev)
 +{
 +      struct fimc_lite *fimc = dev_get_drvdata(dev);
 +
 +      clk_disable(fimc->clock);
 +      return 0;
 +}
 +
 +#ifdef CONFIG_PM_SLEEP
 +static int fimc_lite_resume(struct device *dev)
 +{
 +      struct fimc_lite *fimc = dev_get_drvdata(dev);
 +      struct flite_buffer *buf;
 +      unsigned long flags;
 +      int i;
 +
 +      spin_lock_irqsave(&fimc->slock, flags);
 +      if (!test_and_clear_bit(ST_LPM, &fimc->state) ||
 +          !test_bit(ST_FLITE_IN_USE, &fimc->state)) {
 +              spin_unlock_irqrestore(&fimc->slock, flags);
 +              return 0;
 +      }
 +      flite_hw_reset(fimc);
 +      spin_unlock_irqrestore(&fimc->slock, flags);
 +
 +      if (!test_and_clear_bit(ST_FLITE_SUSPENDED, &fimc->state))
 +              return 0;
 +
 +      INIT_LIST_HEAD(&fimc->active_buf_q);
 +      fimc_pipeline_call(fimc, open, &fimc->pipeline,
 +                         &fimc->vfd.entity, false);
 +      fimc_lite_hw_init(fimc, atomic_read(&fimc->out_path) == FIMC_IO_ISP);
 +      clear_bit(ST_FLITE_SUSPENDED, &fimc->state);
 +
 +      for (i = 0; i < fimc->reqbufs_count; i++) {
 +              if (list_empty(&fimc->pending_buf_q))
 +                      break;
 +              buf = fimc_lite_pending_queue_pop(fimc);
 +              buffer_queue(&buf->vb);
 +      }
 +      return 0;
 +}
 +
 +static int fimc_lite_suspend(struct device *dev)
 +{
 +      struct fimc_lite *fimc = dev_get_drvdata(dev);
 +      bool suspend = test_bit(ST_FLITE_IN_USE, &fimc->state);
 +      int ret;
 +
 +      if (test_and_set_bit(ST_LPM, &fimc->state))
 +              return 0;
 +
 +      ret = fimc_lite_stop_capture(fimc, suspend);
 +      if (ret < 0 || !fimc_lite_active(fimc))
 +              return ret;
 +
 +      return fimc_pipeline_call(fimc, close, &fimc->pipeline);
 +}
 +#endif /* CONFIG_PM_SLEEP */
 +
 +static int fimc_lite_remove(struct platform_device *pdev)
 +{
 +      struct fimc_lite *fimc = platform_get_drvdata(pdev);
 +      struct device *dev = &pdev->dev;
 +
 +      pm_runtime_disable(dev);
 +      pm_runtime_set_suspended(dev);
 +      fimc_lite_unregister_capture_subdev(fimc);
 +      vb2_dma_contig_cleanup_ctx(fimc->alloc_ctx);
 +      fimc_lite_clk_put(fimc);
 +
 +      dev_info(dev, "Driver unloaded\n");
 +      return 0;
 +}
 +
 +static const struct dev_pm_ops fimc_lite_pm_ops = {
 +      SET_SYSTEM_SLEEP_PM_OPS(fimc_lite_suspend, fimc_lite_resume)
 +      SET_RUNTIME_PM_OPS(fimc_lite_runtime_suspend, fimc_lite_runtime_resume,
 +                         NULL)
 +};
 +
 +static struct flite_variant fimc_lite0_variant_exynos4 = {
 +      .max_width              = 8192,
 +      .max_height             = 8192,
 +      .out_width_align        = 8,
 +      .win_hor_offs_align     = 2,
 +      .out_hor_offs_align     = 8,
 +};
 +
 +/* EXYNOS4212, EXYNOS4412 */
 +static struct flite_drvdata fimc_lite_drvdata_exynos4 = {
 +      .variant = {
 +              [0] = &fimc_lite0_variant_exynos4,
 +              [1] = &fimc_lite0_variant_exynos4,
 +      },
 +};
 +
 +static struct platform_device_id fimc_lite_driver_ids[] = {
 +      {
 +              .name           = "exynos-fimc-lite",
 +              .driver_data    = (unsigned long)&fimc_lite_drvdata_exynos4,
 +      },
 +      { /* sentinel */ },
 +};
 +MODULE_DEVICE_TABLE(platform, fimc_lite_driver_ids);
 +
 +static const struct of_device_id flite_of_match[] = {
 +      {
 +              .compatible = "samsung,exynos4212-fimc-lite",
 +              .data = &fimc_lite_drvdata_exynos4,
 +      },
 +      { /* sentinel */ },
 +};
 +MODULE_DEVICE_TABLE(of, flite_of_match);
 +
 +static struct platform_driver fimc_lite_driver = {
 +      .probe          = fimc_lite_probe,
 +      .remove         = fimc_lite_remove,
 +      .id_table       = fimc_lite_driver_ids,
 +      .driver = {
 +              .of_match_table = flite_of_match,
 +              .name           = FIMC_LITE_DRV_NAME,
 +              .owner          = THIS_MODULE,
 +              .pm             = &fimc_lite_pm_ops,
 +      }
 +};
 +module_platform_driver(fimc_lite_driver);
 +MODULE_LICENSE("GPL");
 +MODULE_ALIAS("platform:" FIMC_LITE_DRV_NAME);
index 4a0057708aafc62d9de2a73ff0ed2f9152e6d091,0000000000000000000000000000000000000000..a0fd8cce18df2434698d57a9bcedc981c0684bbf
mode 100644,000000..100644
--- /dev/null
@@@ -1,1437 -1,0 +1,1436 @@@
-       int ret = 0;
 +/*
 + * S5P/EXYNOS4 SoC series camera host interface media device driver
 + *
 + * Copyright (C) 2011 - 2012 Samsung Electronics Co., Ltd.
 + * Sylwester Nawrocki <s.nawrocki@samsung.com>
 + *
 + * This program is free software; you can redistribute it and/or modify
 + * it under the terms of the GNU General Public License as published
 + * by the Free Software Foundation, either version 2 of the License,
 + * or (at your option) any later version.
 + */
 +
 +#include <linux/bug.h>
 +#include <linux/device.h>
 +#include <linux/errno.h>
 +#include <linux/i2c.h>
 +#include <linux/kernel.h>
 +#include <linux/list.h>
 +#include <linux/module.h>
 +#include <linux/of.h>
 +#include <linux/of_platform.h>
 +#include <linux/of_device.h>
 +#include <linux/of_i2c.h>
 +#include <linux/platform_device.h>
 +#include <linux/pm_runtime.h>
 +#include <linux/types.h>
 +#include <linux/slab.h>
 +#include <media/v4l2-ctrls.h>
 +#include <media/v4l2-of.h>
 +#include <media/media-device.h>
 +#include <media/s5p_fimc.h>
 +
 +#include "media-dev.h"
 +#include "fimc-core.h"
 +#include "fimc-lite.h"
 +#include "mipi-csis.h"
 +
 +static int __fimc_md_set_camclk(struct fimc_md *fmd,
 +                              struct fimc_sensor_info *s_info,
 +                              bool on);
 +/**
 + * fimc_pipeline_prepare - update pipeline information with subdevice pointers
 + * @me: media entity terminating the pipeline
 + *
 + * Caller holds the graph mutex.
 + */
 +static void fimc_pipeline_prepare(struct fimc_pipeline *p,
 +                                struct media_entity *me)
 +{
 +      struct v4l2_subdev *sd;
 +      int i;
 +
 +      for (i = 0; i < IDX_MAX; i++)
 +              p->subdevs[i] = NULL;
 +
 +      while (1) {
 +              struct media_pad *pad = NULL;
 +
 +              /* Find remote source pad */
 +              for (i = 0; i < me->num_pads; i++) {
 +                      struct media_pad *spad = &me->pads[i];
 +                      if (!(spad->flags & MEDIA_PAD_FL_SINK))
 +                              continue;
 +                      pad = media_entity_remote_source(spad);
 +                      if (pad)
 +                              break;
 +              }
 +
 +              if (pad == NULL ||
 +                  media_entity_type(pad->entity) != MEDIA_ENT_T_V4L2_SUBDEV)
 +                      break;
 +              sd = media_entity_to_v4l2_subdev(pad->entity);
 +
 +              switch (sd->grp_id) {
 +              case GRP_ID_FIMC_IS_SENSOR:
 +              case GRP_ID_SENSOR:
 +                      p->subdevs[IDX_SENSOR] = sd;
 +                      break;
 +              case GRP_ID_CSIS:
 +                      p->subdevs[IDX_CSIS] = sd;
 +                      break;
 +              case GRP_ID_FLITE:
 +                      p->subdevs[IDX_FLITE] = sd;
 +                      break;
 +              case GRP_ID_FIMC:
 +                      /* No need to control FIMC subdev through subdev ops */
 +                      break;
 +              default:
 +                      pr_warn("%s: Unknown subdev grp_id: %#x\n",
 +                              __func__, sd->grp_id);
 +              }
 +              me = &sd->entity;
 +              if (me->num_pads == 1)
 +                      break;
 +      }
 +}
 +
 +/**
 + * __subdev_set_power - change power state of a single subdev
 + * @sd: subdevice to change power state for
 + * @on: 1 to enable power or 0 to disable
 + *
 + * Return result of s_power subdev operation or -ENXIO if sd argument
 + * is NULL. Return 0 if the subdevice does not implement s_power.
 + */
 +static int __subdev_set_power(struct v4l2_subdev *sd, int on)
 +{
 +      int *use_count;
 +      int ret;
 +
 +      if (sd == NULL)
 +              return -ENXIO;
 +
 +      use_count = &sd->entity.use_count;
 +      if (on && (*use_count)++ > 0)
 +              return 0;
 +      else if (!on && (*use_count == 0 || --(*use_count) > 0))
 +              return 0;
 +      ret = v4l2_subdev_call(sd, core, s_power, on);
 +
 +      return ret != -ENOIOCTLCMD ? ret : 0;
 +}
 +
 +/**
 + * fimc_pipeline_s_power - change power state of all pipeline subdevs
 + * @fimc: fimc device terminating the pipeline
 + * @state: true to power on, false to power off
 + *
 + * Needs to be called with the graph mutex held.
 + */
 +static int fimc_pipeline_s_power(struct fimc_pipeline *p, bool on)
 +{
 +      static const u8 seq[2][IDX_MAX - 1] = {
 +              { IDX_IS_ISP, IDX_SENSOR, IDX_CSIS, IDX_FLITE },
 +              { IDX_CSIS, IDX_FLITE, IDX_SENSOR, IDX_IS_ISP },
 +      };
 +      int i, ret = 0;
 +
 +      if (p->subdevs[IDX_SENSOR] == NULL)
 +              return -ENXIO;
 +
 +      for (i = 0; i < IDX_MAX - 1; i++) {
 +              unsigned int idx = seq[on][i];
 +
 +              ret = __subdev_set_power(p->subdevs[idx], on);
 +
 +
 +              if (ret < 0 && ret != -ENXIO)
 +                      goto error;
 +      }
 +      return 0;
 +error:
 +      for (; i >= 0; i--) {
 +              unsigned int idx = seq[on][i];
 +              __subdev_set_power(p->subdevs[idx], !on);
 +      }
 +      return ret;
 +}
 +
 +/**
 + * __fimc_pipeline_open - update the pipeline information, enable power
 + *                        of all pipeline subdevs and the sensor clock
 + * @me: media entity to start graph walk with
 + * @prepare: true to walk the current pipeline and acquire all subdevs
 + *
 + * Called with the graph mutex held.
 + */
 +static int __fimc_pipeline_open(struct fimc_pipeline *p,
 +                              struct media_entity *me, bool prepare)
 +{
 +      struct fimc_md *fmd = entity_to_fimc_mdev(me);
 +      struct v4l2_subdev *sd;
 +      int ret;
 +
 +      if (WARN_ON(p == NULL || me == NULL))
 +              return -EINVAL;
 +
 +      if (prepare)
 +              fimc_pipeline_prepare(p, me);
 +
 +      sd = p->subdevs[IDX_SENSOR];
 +      if (sd == NULL)
 +              return -EINVAL;
 +
 +      /* Disable PXLASYNC clock if this pipeline includes FIMC-IS */
 +      if (!IS_ERR(fmd->wbclk[CLK_IDX_WB_B]) && p->subdevs[IDX_IS_ISP]) {
 +              ret = clk_prepare_enable(fmd->wbclk[CLK_IDX_WB_B]);
 +              if (ret < 0)
 +                      return ret;
 +      }
 +      ret = fimc_md_set_camclk(sd, true);
 +      if (ret < 0)
 +              goto err_wbclk;
 +
 +      ret = fimc_pipeline_s_power(p, 1);
 +      if (!ret)
 +              return 0;
 +
 +      fimc_md_set_camclk(sd, false);
 +
 +err_wbclk:
 +      if (!IS_ERR(fmd->wbclk[CLK_IDX_WB_B]) && p->subdevs[IDX_IS_ISP])
 +              clk_disable_unprepare(fmd->wbclk[CLK_IDX_WB_B]);
 +
 +      return ret;
 +}
 +
 +/**
 + * __fimc_pipeline_close - disable the sensor clock and pipeline power
 + * @fimc: fimc device terminating the pipeline
 + *
 + * Disable power of all subdevs and turn the external sensor clock off.
 + */
 +static int __fimc_pipeline_close(struct fimc_pipeline *p)
 +{
 +      struct v4l2_subdev *sd = p ? p->subdevs[IDX_SENSOR] : NULL;
 +      struct fimc_md *fmd;
 +      int ret = 0;
 +
 +      if (WARN_ON(sd == NULL))
 +              return -EINVAL;
 +
 +      if (p->subdevs[IDX_SENSOR]) {
 +              ret = fimc_pipeline_s_power(p, 0);
 +              fimc_md_set_camclk(sd, false);
 +      }
 +
 +      fmd = entity_to_fimc_mdev(&sd->entity);
 +
 +      /* Disable PXLASYNC clock if this pipeline includes FIMC-IS */
 +      if (!IS_ERR(fmd->wbclk[CLK_IDX_WB_B]) && p->subdevs[IDX_IS_ISP])
 +              clk_disable_unprepare(fmd->wbclk[CLK_IDX_WB_B]);
 +
 +      return ret == -ENXIO ? 0 : ret;
 +}
 +
 +/**
 + * __fimc_pipeline_s_stream - call s_stream() on pipeline subdevs
 + * @pipeline: video pipeline structure
 + * @on: passed as the s_stream() callback argument
 + */
 +static int __fimc_pipeline_s_stream(struct fimc_pipeline *p, bool on)
 +{
 +      static const u8 seq[2][IDX_MAX] = {
 +              { IDX_FIMC, IDX_SENSOR, IDX_IS_ISP, IDX_CSIS, IDX_FLITE },
 +              { IDX_CSIS, IDX_FLITE, IDX_FIMC, IDX_SENSOR, IDX_IS_ISP },
 +      };
 +      int i, ret = 0;
 +
 +      if (p->subdevs[IDX_SENSOR] == NULL)
 +              return -ENODEV;
 +
 +      for (i = 0; i < IDX_MAX; i++) {
 +              unsigned int idx = seq[on][i];
 +
 +              ret = v4l2_subdev_call(p->subdevs[idx], video, s_stream, on);
 +
 +              if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
 +                      goto error;
 +      }
 +      return 0;
 +error:
 +      for (; i >= 0; i--) {
 +              unsigned int idx = seq[on][i];
 +              v4l2_subdev_call(p->subdevs[idx], video, s_stream, !on);
 +      }
 +      return ret;
 +}
 +
 +/* Media pipeline operations for the FIMC/FIMC-LITE video device driver */
 +static const struct fimc_pipeline_ops fimc_pipeline_ops = {
 +      .open           = __fimc_pipeline_open,
 +      .close          = __fimc_pipeline_close,
 +      .set_stream     = __fimc_pipeline_s_stream,
 +};
 +
 +/*
 + * Sensor subdevice helper functions
 + */
 +static struct v4l2_subdev *fimc_md_register_sensor(struct fimc_md *fmd,
 +                                 struct fimc_sensor_info *s_info)
 +{
 +      struct i2c_adapter *adapter;
 +      struct v4l2_subdev *sd = NULL;
 +
 +      if (!s_info || !fmd)
 +              return NULL;
 +      /*
 +       * If FIMC bus type is not Writeback FIFO assume it is same
 +       * as sensor_bus_type.
 +       */
 +      s_info->pdata.fimc_bus_type = s_info->pdata.sensor_bus_type;
 +
 +      adapter = i2c_get_adapter(s_info->pdata.i2c_bus_num);
 +      if (!adapter) {
 +              v4l2_warn(&fmd->v4l2_dev,
 +                        "Failed to get I2C adapter %d, deferring probe\n",
 +                        s_info->pdata.i2c_bus_num);
 +              return ERR_PTR(-EPROBE_DEFER);
 +      }
 +      sd = v4l2_i2c_new_subdev_board(&fmd->v4l2_dev, adapter,
 +                                     s_info->pdata.board_info, NULL);
 +      if (IS_ERR_OR_NULL(sd)) {
 +              i2c_put_adapter(adapter);
 +              v4l2_warn(&fmd->v4l2_dev,
 +                        "Failed to acquire subdev %s, deferring probe\n",
 +                        s_info->pdata.board_info->type);
 +              return ERR_PTR(-EPROBE_DEFER);
 +      }
 +      v4l2_set_subdev_hostdata(sd, s_info);
 +      sd->grp_id = GRP_ID_SENSOR;
 +
 +      v4l2_info(&fmd->v4l2_dev, "Registered sensor subdevice %s\n",
 +                sd->name);
 +      return sd;
 +}
 +
 +static void fimc_md_unregister_sensor(struct v4l2_subdev *sd)
 +{
 +      struct i2c_client *client = v4l2_get_subdevdata(sd);
 +      struct i2c_adapter *adapter;
 +
 +      if (!client)
 +              return;
 +      v4l2_device_unregister_subdev(sd);
 +
 +      if (!client->dev.of_node) {
 +              adapter = client->adapter;
 +              i2c_unregister_device(client);
 +              if (adapter)
 +                      i2c_put_adapter(adapter);
 +      }
 +}
 +
 +#ifdef CONFIG_OF
 +/* Register I2C client subdev associated with @node. */
 +static int fimc_md_of_add_sensor(struct fimc_md *fmd,
 +                               struct device_node *node, int index)
 +{
 +      struct fimc_sensor_info *si;
 +      struct i2c_client *client;
 +      struct v4l2_subdev *sd;
 +      int ret;
 +
 +      if (WARN_ON(index >= ARRAY_SIZE(fmd->sensor)))
 +              return -EINVAL;
 +      si = &fmd->sensor[index];
 +
 +      client = of_find_i2c_device_by_node(node);
 +      if (!client)
 +              return -EPROBE_DEFER;
 +
 +      device_lock(&client->dev);
 +
 +      if (!client->driver ||
 +          !try_module_get(client->driver->driver.owner)) {
 +              ret = -EPROBE_DEFER;
 +              v4l2_info(&fmd->v4l2_dev, "No driver found for %s\n",
 +                                              node->full_name);
 +              goto dev_put;
 +      }
 +
 +      /* Enable sensor's master clock */
 +      ret = __fimc_md_set_camclk(fmd, si, true);
 +      if (ret < 0)
 +              goto mod_put;
 +      sd = i2c_get_clientdata(client);
 +
 +      ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd);
 +      __fimc_md_set_camclk(fmd, si, false);
 +      if (ret < 0)
 +              goto mod_put;
 +
 +      v4l2_set_subdev_hostdata(sd, si);
 +      sd->grp_id = GRP_ID_SENSOR;
 +      si->subdev = sd;
 +      v4l2_info(&fmd->v4l2_dev, "Registered sensor subdevice: %s (%d)\n",
 +                sd->name, fmd->num_sensors);
 +      fmd->num_sensors++;
 +
 +mod_put:
 +      module_put(client->driver->driver.owner);
 +dev_put:
 +      device_unlock(&client->dev);
 +      put_device(&client->dev);
 +      return ret;
 +}
 +
 +/* Parse port node and register as a sub-device any sensor specified there. */
 +static int fimc_md_parse_port_node(struct fimc_md *fmd,
 +                                 struct device_node *port,
 +                                 unsigned int index)
 +{
 +      struct device_node *rem, *ep, *np;
 +      struct fimc_source_info *pd;
 +      struct v4l2_of_endpoint endpoint;
 +      int ret;
 +      u32 val;
 +
 +      pd = &fmd->sensor[index].pdata;
 +
 +      /* Assume here a port node can have only one endpoint node. */
 +      ep = of_get_next_child(port, NULL);
 +      if (!ep)
 +              return 0;
 +
 +      v4l2_of_parse_endpoint(ep, &endpoint);
 +      if (WARN_ON(endpoint.port == 0) || index >= FIMC_MAX_SENSORS)
 +              return -EINVAL;
 +
 +      pd->mux_id = (endpoint.port - 1) & 0x1;
 +
 +      rem = v4l2_of_get_remote_port_parent(ep);
 +      of_node_put(ep);
 +      if (rem == NULL) {
 +              v4l2_info(&fmd->v4l2_dev, "Remote device at %s not found\n",
 +                                                      ep->full_name);
 +              return 0;
 +      }
 +      if (!of_property_read_u32(rem, "samsung,camclk-out", &val))
 +              pd->clk_id = val;
 +
 +      if (!of_property_read_u32(rem, "clock-frequency", &val))
 +              pd->clk_frequency = val;
 +
 +      if (pd->clk_frequency == 0) {
 +              v4l2_err(&fmd->v4l2_dev, "Wrong clock frequency at node %s\n",
 +                       rem->full_name);
 +              of_node_put(rem);
 +              return -EINVAL;
 +      }
 +
 +      if (fimc_input_is_parallel(endpoint.port)) {
 +              if (endpoint.bus_type == V4L2_MBUS_PARALLEL)
 +                      pd->sensor_bus_type = FIMC_BUS_TYPE_ITU_601;
 +              else
 +                      pd->sensor_bus_type = FIMC_BUS_TYPE_ITU_656;
 +              pd->flags = endpoint.bus.parallel.flags;
 +      } else if (fimc_input_is_mipi_csi(endpoint.port)) {
 +              /*
 +               * MIPI CSI-2: only input mux selection and
 +               * the sensor's clock frequency is needed.
 +               */
 +              pd->sensor_bus_type = FIMC_BUS_TYPE_MIPI_CSI2;
 +      } else {
 +              v4l2_err(&fmd->v4l2_dev, "Wrong port id (%u) at node %s\n",
 +                       endpoint.port, rem->full_name);
 +      }
 +      /*
 +       * For FIMC-IS handled sensors, that are placed under i2c-isp device
 +       * node, FIMC is connected to the FIMC-IS through its ISP Writeback
 +       * input. Sensors are attached to the FIMC-LITE hostdata interface
 +       * directly or through MIPI-CSIS, depending on the external media bus
 +       * used. This needs to be handled in a more reliable way, not by just
 +       * checking parent's node name.
 +       */
 +      np = of_get_parent(rem);
 +
 +      if (np && !of_node_cmp(np->name, "i2c-isp"))
 +              pd->fimc_bus_type = FIMC_BUS_TYPE_ISP_WRITEBACK;
 +      else
 +              pd->fimc_bus_type = pd->sensor_bus_type;
 +
 +      ret = fimc_md_of_add_sensor(fmd, rem, index);
 +      of_node_put(rem);
 +
 +      return ret;
 +}
 +
 +/* Register all SoC external sub-devices */
 +static int fimc_md_of_sensors_register(struct fimc_md *fmd,
 +                                     struct device_node *np)
 +{
 +      struct device_node *parent = fmd->pdev->dev.of_node;
 +      struct device_node *node, *ports;
 +      int index = 0;
 +      int ret;
 +
 +      /* Attach sensors linked to MIPI CSI-2 receivers */
 +      for_each_available_child_of_node(parent, node) {
 +              struct device_node *port;
 +
 +              if (of_node_cmp(node->name, "csis"))
 +                      continue;
 +              /* The csis node can have only port subnode. */
 +              port = of_get_next_child(node, NULL);
 +              if (!port)
 +                      continue;
 +
 +              ret = fimc_md_parse_port_node(fmd, port, index);
 +              if (ret < 0)
 +                      return ret;
 +              index++;
 +      }
 +
 +      /* Attach sensors listed in the parallel-ports node */
 +      ports = of_get_child_by_name(parent, "parallel-ports");
 +      if (!ports)
 +              return 0;
 +
 +      for_each_child_of_node(ports, node) {
 +              ret = fimc_md_parse_port_node(fmd, node, index);
 +              if (ret < 0)
 +                      break;
 +              index++;
 +      }
 +
 +      return 0;
 +}
 +
 +static int __of_get_csis_id(struct device_node *np)
 +{
 +      u32 reg = 0;
 +
 +      np = of_get_child_by_name(np, "port");
 +      if (!np)
 +              return -EINVAL;
 +      of_property_read_u32(np, "reg", &reg);
 +      return reg - FIMC_INPUT_MIPI_CSI2_0;
 +}
 +#else
 +#define fimc_md_of_sensors_register(fmd, np) (-ENOSYS)
 +#define __of_get_csis_id(np) (-ENOSYS)
 +#endif
 +
 +static int fimc_md_register_sensor_entities(struct fimc_md *fmd)
 +{
 +      struct s5p_platform_fimc *pdata = fmd->pdev->dev.platform_data;
 +      struct device_node *of_node = fmd->pdev->dev.of_node;
 +      int num_clients = 0;
 +      int ret, i;
 +
 +      /*
 +       * Runtime resume one of the FIMC entities to make sure
 +       * the sclk_cam clocks are not globally disabled.
 +       */
 +      if (!fmd->pmf)
 +              return -ENXIO;
 +
 +      ret = pm_runtime_get_sync(fmd->pmf);
 +      if (ret < 0)
 +              return ret;
 +
 +      if (of_node) {
 +              fmd->num_sensors = 0;
 +              ret = fimc_md_of_sensors_register(fmd, of_node);
 +      } else if (pdata) {
 +              WARN_ON(pdata->num_clients > ARRAY_SIZE(fmd->sensor));
 +              num_clients = min_t(u32, pdata->num_clients,
 +                                  ARRAY_SIZE(fmd->sensor));
 +              fmd->num_sensors = num_clients;
 +
 +              for (i = 0; i < num_clients; i++) {
 +                      struct v4l2_subdev *sd;
 +
 +                      fmd->sensor[i].pdata = pdata->source_info[i];
 +                      ret = __fimc_md_set_camclk(fmd, &fmd->sensor[i], true);
 +                      if (ret)
 +                              break;
 +                      sd = fimc_md_register_sensor(fmd, &fmd->sensor[i]);
 +                      ret = __fimc_md_set_camclk(fmd, &fmd->sensor[i], false);
 +
 +                      if (IS_ERR(sd)) {
 +                              fmd->sensor[i].subdev = NULL;
 +                              ret = PTR_ERR(sd);
 +                              break;
 +                      }
 +                      fmd->sensor[i].subdev = sd;
 +                      if (ret)
 +                              break;
 +              }
 +      }
 +
 +      pm_runtime_put(fmd->pmf);
 +      return ret;
 +}
 +
 +/*
 + * MIPI-CSIS, FIMC and FIMC-LITE platform devices registration.
 + */
 +
 +static int register_fimc_lite_entity(struct fimc_md *fmd,
 +                                   struct fimc_lite *fimc_lite)
 +{
 +      struct v4l2_subdev *sd;
 +      int ret;
 +
 +      if (WARN_ON(fimc_lite->index >= FIMC_LITE_MAX_DEVS ||
 +                  fmd->fimc_lite[fimc_lite->index]))
 +              return -EBUSY;
 +
 +      sd = &fimc_lite->subdev;
 +      sd->grp_id = GRP_ID_FLITE;
 +      v4l2_set_subdev_hostdata(sd, (void *)&fimc_pipeline_ops);
 +
 +      ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd);
 +      if (!ret)
 +              fmd->fimc_lite[fimc_lite->index] = fimc_lite;
 +      else
 +              v4l2_err(&fmd->v4l2_dev, "Failed to register FIMC.LITE%d\n",
 +                       fimc_lite->index);
 +      return ret;
 +}
 +
 +static int register_fimc_entity(struct fimc_md *fmd, struct fimc_dev *fimc)
 +{
 +      struct v4l2_subdev *sd;
 +      int ret;
 +
 +      if (WARN_ON(fimc->id >= FIMC_MAX_DEVS || fmd->fimc[fimc->id]))
 +              return -EBUSY;
 +
 +      sd = &fimc->vid_cap.subdev;
 +      sd->grp_id = GRP_ID_FIMC;
 +      v4l2_set_subdev_hostdata(sd, (void *)&fimc_pipeline_ops);
 +
 +      ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd);
 +      if (!ret) {
 +              if (!fmd->pmf && fimc->pdev)
 +                      fmd->pmf = &fimc->pdev->dev;
 +              fmd->fimc[fimc->id] = fimc;
 +              fimc->vid_cap.user_subdev_api = fmd->user_subdev_api;
 +      } else {
 +              v4l2_err(&fmd->v4l2_dev, "Failed to register FIMC.%d (%d)\n",
 +                       fimc->id, ret);
 +      }
 +      return ret;
 +}
 +
 +static int register_csis_entity(struct fimc_md *fmd,
 +                              struct platform_device *pdev,
 +                              struct v4l2_subdev *sd)
 +{
 +      struct device_node *node = pdev->dev.of_node;
 +      int id, ret;
 +
 +      id = node ? __of_get_csis_id(node) : max(0, pdev->id);
 +
 +      if (WARN_ON(id < 0 || id >= CSIS_MAX_ENTITIES))
 +              return -ENOENT;
 +
 +      if (WARN_ON(fmd->csis[id].sd))
 +              return -EBUSY;
 +
 +      sd->grp_id = GRP_ID_CSIS;
 +      ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd);
 +      if (!ret)
 +              fmd->csis[id].sd = sd;
 +      else
 +              v4l2_err(&fmd->v4l2_dev,
 +                       "Failed to register MIPI-CSIS.%d (%d)\n", id, ret);
 +      return ret;
 +}
 +
 +static int fimc_md_register_platform_entity(struct fimc_md *fmd,
 +                                          struct platform_device *pdev,
 +                                          int plat_entity)
 +{
 +      struct device *dev = &pdev->dev;
 +      int ret = -EPROBE_DEFER;
 +      void *drvdata;
 +
 +      /* Lock to ensure dev->driver won't change. */
 +      device_lock(dev);
 +
 +      if (!dev->driver || !try_module_get(dev->driver->owner))
 +              goto dev_unlock;
 +
 +      drvdata = dev_get_drvdata(dev);
 +      /* Some subdev didn't probe succesfully id drvdata is NULL */
 +      if (drvdata) {
 +              switch (plat_entity) {
 +              case IDX_FIMC:
 +                      ret = register_fimc_entity(fmd, drvdata);
 +                      break;
 +              case IDX_FLITE:
 +                      ret = register_fimc_lite_entity(fmd, drvdata);
 +                      break;
 +              case IDX_CSIS:
 +                      ret = register_csis_entity(fmd, pdev, drvdata);
 +                      break;
 +              default:
 +                      ret = -ENODEV;
 +              }
 +      }
 +
 +      module_put(dev->driver->owner);
 +dev_unlock:
 +      device_unlock(dev);
 +      if (ret == -EPROBE_DEFER)
 +              dev_info(&fmd->pdev->dev, "deferring %s device registration\n",
 +                      dev_name(dev));
 +      else if (ret < 0)
 +              dev_err(&fmd->pdev->dev, "%s device registration failed (%d)\n",
 +                      dev_name(dev), ret);
 +      return ret;
 +}
 +
 +static int fimc_md_pdev_match(struct device *dev, void *data)
 +{
 +      struct platform_device *pdev = to_platform_device(dev);
 +      int plat_entity = -1;
 +      int ret;
 +      char *p;
 +
 +      if (!get_device(dev))
 +              return -ENODEV;
 +
 +      if (!strcmp(pdev->name, CSIS_DRIVER_NAME)) {
 +              plat_entity = IDX_CSIS;
 +      } else if (!strcmp(pdev->name, FIMC_LITE_DRV_NAME)) {
 +              plat_entity = IDX_FLITE;
 +      } else {
 +              p = strstr(pdev->name, "fimc");
 +              if (p && *(p + 4) == 0)
 +                      plat_entity = IDX_FIMC;
 +      }
 +
 +      if (plat_entity >= 0)
 +              ret = fimc_md_register_platform_entity(data, pdev,
 +                                                     plat_entity);
 +      put_device(dev);
 +      return 0;
 +}
 +
 +/* Register FIMC, FIMC-LITE and CSIS media entities */
 +#ifdef CONFIG_OF
 +static int fimc_md_register_of_platform_entities(struct fimc_md *fmd,
 +                                               struct device_node *parent)
 +{
 +      struct device_node *node;
 +      int ret = 0;
 +
 +      for_each_available_child_of_node(parent, node) {
 +              struct platform_device *pdev;
 +              int plat_entity = -1;
 +
 +              pdev = of_find_device_by_node(node);
 +              if (!pdev)
 +                      continue;
 +
 +              /* If driver of any entity isn't ready try all again later. */
 +              if (!strcmp(node->name, CSIS_OF_NODE_NAME))
 +                      plat_entity = IDX_CSIS;
 +              else if (!strcmp(node->name, FIMC_LITE_OF_NODE_NAME))
 +                      plat_entity = IDX_FLITE;
 +              else if (!strcmp(node->name, FIMC_OF_NODE_NAME) &&
 +                       !of_property_read_bool(node, "samsung,lcd-wb"))
 +                      plat_entity = IDX_FIMC;
 +
 +              if (plat_entity >= 0)
 +                      ret = fimc_md_register_platform_entity(fmd, pdev,
 +                                                      plat_entity);
 +              put_device(&pdev->dev);
 +              if (ret < 0)
 +                      break;
 +      }
 +
 +      return ret;
 +}
 +#else
 +#define fimc_md_register_of_platform_entities(fmd, node) (-ENOSYS)
 +#endif
 +
 +static void fimc_md_unregister_entities(struct fimc_md *fmd)
 +{
 +      int i;
 +
 +      for (i = 0; i < FIMC_MAX_DEVS; i++) {
 +              if (fmd->fimc[i] == NULL)
 +                      continue;
 +              v4l2_device_unregister_subdev(&fmd->fimc[i]->vid_cap.subdev);
 +              fmd->fimc[i]->pipeline_ops = NULL;
 +              fmd->fimc[i] = NULL;
 +      }
 +      for (i = 0; i < FIMC_LITE_MAX_DEVS; i++) {
 +              if (fmd->fimc_lite[i] == NULL)
 +                      continue;
 +              v4l2_device_unregister_subdev(&fmd->fimc_lite[i]->subdev);
 +              fmd->fimc_lite[i]->pipeline_ops = NULL;
 +              fmd->fimc_lite[i] = NULL;
 +      }
 +      for (i = 0; i < CSIS_MAX_ENTITIES; i++) {
 +              if (fmd->csis[i].sd == NULL)
 +                      continue;
 +              v4l2_device_unregister_subdev(fmd->csis[i].sd);
 +              module_put(fmd->csis[i].sd->owner);
 +              fmd->csis[i].sd = NULL;
 +      }
 +      for (i = 0; i < fmd->num_sensors; i++) {
 +              if (fmd->sensor[i].subdev == NULL)
 +                      continue;
 +              fimc_md_unregister_sensor(fmd->sensor[i].subdev);
 +              fmd->sensor[i].subdev = NULL;
 +      }
 +      v4l2_info(&fmd->v4l2_dev, "Unregistered all entities\n");
 +}
 +
 +/**
 + * __fimc_md_create_fimc_links - create links to all FIMC entities
 + * @fmd: fimc media device
 + * @source: the source entity to create links to all fimc entities from
 + * @sensor: sensor subdev linked to FIMC[fimc_id] entity, may be null
 + * @pad: the source entity pad index
 + * @link_mask: bitmask of the fimc devices for which link should be enabled
 + */
 +static int __fimc_md_create_fimc_sink_links(struct fimc_md *fmd,
 +                                          struct media_entity *source,
 +                                          struct v4l2_subdev *sensor,
 +                                          int pad, int link_mask)
 +{
 +      struct fimc_sensor_info *s_info = NULL;
 +      struct media_entity *sink;
 +      unsigned int flags = 0;
 +      int ret, i;
 +
 +      for (i = 0; i < FIMC_MAX_DEVS; i++) {
 +              if (!fmd->fimc[i])
 +                      continue;
 +              /*
 +               * Some FIMC variants are not fitted with camera capture
 +               * interface. Skip creating a link from sensor for those.
 +               */
 +              if (!fmd->fimc[i]->variant->has_cam_if)
 +                      continue;
 +
 +              flags = ((1 << i) & link_mask) ? MEDIA_LNK_FL_ENABLED : 0;
 +
 +              sink = &fmd->fimc[i]->vid_cap.subdev.entity;
 +              ret = media_entity_create_link(source, pad, sink,
 +                                            FIMC_SD_PAD_SINK_CAM, flags);
 +              if (ret)
 +                      return ret;
 +
 +              /* Notify FIMC capture subdev entity */
 +              ret = media_entity_call(sink, link_setup, &sink->pads[0],
 +                                      &source->pads[pad], flags);
 +              if (ret)
 +                      break;
 +
 +              v4l2_info(&fmd->v4l2_dev, "created link [%s] %c> [%s]\n",
 +                        source->name, flags ? '=' : '-', sink->name);
 +
 +              if (flags == 0 || sensor == NULL)
 +                      continue;
 +              s_info = v4l2_get_subdev_hostdata(sensor);
 +              if (!WARN_ON(s_info == NULL)) {
 +                      unsigned long irq_flags;
 +                      spin_lock_irqsave(&fmd->slock, irq_flags);
 +                      s_info->host = fmd->fimc[i];
 +                      spin_unlock_irqrestore(&fmd->slock, irq_flags);
 +              }
 +      }
 +
 +      for (i = 0; i < FIMC_LITE_MAX_DEVS; i++) {
 +              if (!fmd->fimc_lite[i])
 +                      continue;
 +
 +              if (link_mask & (1 << (i + FIMC_MAX_DEVS)))
 +                      flags = MEDIA_LNK_FL_ENABLED;
 +              else
 +                      flags = 0;
 +
 +              sink = &fmd->fimc_lite[i]->subdev.entity;
 +              ret = media_entity_create_link(source, pad, sink,
 +                                             FLITE_SD_PAD_SINK, flags);
 +              if (ret)
 +                      return ret;
 +
 +              /* Notify FIMC-LITE subdev entity */
 +              ret = media_entity_call(sink, link_setup, &sink->pads[0],
 +                                      &source->pads[pad], flags);
 +              if (ret)
 +                      break;
 +
 +              v4l2_info(&fmd->v4l2_dev, "created link [%s] %c> [%s]\n",
 +                        source->name, flags ? '=' : '-', sink->name);
 +      }
 +      return 0;
 +}
 +
 +/* Create links from FIMC-LITE source pads to other entities */
 +static int __fimc_md_create_flite_source_links(struct fimc_md *fmd)
 +{
 +      struct media_entity *source, *sink;
 +      unsigned int flags = MEDIA_LNK_FL_ENABLED;
 +      int i, ret = 0;
 +
 +      for (i = 0; i < FIMC_LITE_MAX_DEVS; i++) {
 +              struct fimc_lite *fimc = fmd->fimc_lite[i];
 +              if (fimc == NULL)
 +                      continue;
 +              source = &fimc->subdev.entity;
 +              sink = &fimc->vfd.entity;
 +              /* FIMC-LITE's subdev and video node */
 +              ret = media_entity_create_link(source, FLITE_SD_PAD_SOURCE_DMA,
 +                                             sink, 0, flags);
 +              if (ret)
 +                      break;
 +              /* TODO: create links to other entities */
 +      }
 +
 +      return ret;
 +}
 +
 +/**
 + * fimc_md_create_links - create default links between registered entities
 + *
 + * Parallel interface sensor entities are connected directly to FIMC capture
 + * entities. The sensors using MIPI CSIS bus are connected through immutable
 + * link with CSI receiver entity specified by mux_id. Any registered CSIS
 + * entity has a link to each registered FIMC capture entity. Enabled links
 + * are created by default between each subsequent registered sensor and
 + * subsequent FIMC capture entity. The number of default active links is
 + * determined by the number of available sensors or FIMC entities,
 + * whichever is less.
 + */
 +static int fimc_md_create_links(struct fimc_md *fmd)
 +{
 +      struct v4l2_subdev *csi_sensors[CSIS_MAX_ENTITIES] = { NULL };
 +      struct v4l2_subdev *sensor, *csis;
 +      struct fimc_source_info *pdata;
 +      struct fimc_sensor_info *s_info;
 +      struct media_entity *source, *sink;
 +      int i, pad, fimc_id = 0, ret = 0;
 +      u32 flags, link_mask = 0;
 +
 +      for (i = 0; i < fmd->num_sensors; i++) {
 +              if (fmd->sensor[i].subdev == NULL)
 +                      continue;
 +
 +              sensor = fmd->sensor[i].subdev;
 +              s_info = v4l2_get_subdev_hostdata(sensor);
 +              if (!s_info)
 +                      continue;
 +
 +              source = NULL;
 +              pdata = &s_info->pdata;
 +
 +              switch (pdata->sensor_bus_type) {
 +              case FIMC_BUS_TYPE_MIPI_CSI2:
 +                      if (WARN(pdata->mux_id >= CSIS_MAX_ENTITIES,
 +                              "Wrong CSI channel id: %d\n", pdata->mux_id))
 +                              return -EINVAL;
 +
 +                      csis = fmd->csis[pdata->mux_id].sd;
 +                      if (WARN(csis == NULL,
 +                               "MIPI-CSI interface specified "
 +                               "but s5p-csis module is not loaded!\n"))
 +                              return -EINVAL;
 +
 +                      pad = sensor->entity.num_pads - 1;
 +                      ret = media_entity_create_link(&sensor->entity, pad,
 +                                            &csis->entity, CSIS_PAD_SINK,
 +                                            MEDIA_LNK_FL_IMMUTABLE |
 +                                            MEDIA_LNK_FL_ENABLED);
 +                      if (ret)
 +                              return ret;
 +
 +                      v4l2_info(&fmd->v4l2_dev, "created link [%s] => [%s]\n",
 +                                sensor->entity.name, csis->entity.name);
 +
 +                      source = NULL;
 +                      csi_sensors[pdata->mux_id] = sensor;
 +                      break;
 +
 +              case FIMC_BUS_TYPE_ITU_601...FIMC_BUS_TYPE_ITU_656:
 +                      source = &sensor->entity;
 +                      pad = 0;
 +                      break;
 +
 +              default:
 +                      v4l2_err(&fmd->v4l2_dev, "Wrong bus_type: %x\n",
 +                               pdata->sensor_bus_type);
 +                      return -EINVAL;
 +              }
 +              if (source == NULL)
 +                      continue;
 +
 +              link_mask = 1 << fimc_id++;
 +              ret = __fimc_md_create_fimc_sink_links(fmd, source, sensor,
 +                                                     pad, link_mask);
 +      }
 +
 +      for (i = 0; i < CSIS_MAX_ENTITIES; i++) {
 +              if (fmd->csis[i].sd == NULL)
 +                      continue;
 +              source = &fmd->csis[i].sd->entity;
 +              pad = CSIS_PAD_SOURCE;
 +              sensor = csi_sensors[i];
 +
 +              link_mask = 1 << fimc_id++;
 +              ret = __fimc_md_create_fimc_sink_links(fmd, source, sensor,
 +                                                     pad, link_mask);
 +      }
 +
 +      /* Create immutable links between each FIMC's subdev and video node */
 +      flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED;
 +      for (i = 0; i < FIMC_MAX_DEVS; i++) {
 +              if (!fmd->fimc[i])
 +                      continue;
 +              source = &fmd->fimc[i]->vid_cap.subdev.entity;
 +              sink = &fmd->fimc[i]->vid_cap.vfd.entity;
 +              ret = media_entity_create_link(source, FIMC_SD_PAD_SOURCE,
 +                                            sink, 0, flags);
 +              if (ret)
 +                      break;
 +      }
 +
 +      return __fimc_md_create_flite_source_links(fmd);
 +}
 +
 +/*
 + * The peripheral sensor and CAM_BLK (PIXELASYNCMx) clocks management.
 + */
 +static void fimc_md_put_clocks(struct fimc_md *fmd)
 +{
 +      int i = FIMC_MAX_CAMCLKS;
 +
 +      while (--i >= 0) {
 +              if (IS_ERR(fmd->camclk[i].clock))
 +                      continue;
 +              clk_unprepare(fmd->camclk[i].clock);
 +              clk_put(fmd->camclk[i].clock);
 +              fmd->camclk[i].clock = ERR_PTR(-EINVAL);
 +      }
 +
 +      /* Writeback (PIXELASYNCMx) clocks */
 +      for (i = 0; i < FIMC_MAX_WBCLKS; i++) {
 +              if (IS_ERR(fmd->wbclk[i]))
 +                      continue;
 +              clk_put(fmd->wbclk[i]);
 +              fmd->wbclk[i] = ERR_PTR(-EINVAL);
 +      }
 +}
 +
 +static int fimc_md_get_clocks(struct fimc_md *fmd)
 +{
 +      struct device *dev = NULL;
 +      char clk_name[32];
 +      struct clk *clock;
 +      int ret, i;
 +
 +      for (i = 0; i < FIMC_MAX_CAMCLKS; i++)
 +              fmd->camclk[i].clock = ERR_PTR(-EINVAL);
 +
 +      if (fmd->pdev->dev.of_node)
 +              dev = &fmd->pdev->dev;
 +
 +      for (i = 0; i < FIMC_MAX_CAMCLKS; i++) {
 +              snprintf(clk_name, sizeof(clk_name), "sclk_cam%u", i);
 +              clock = clk_get(dev, clk_name);
 +
 +              if (IS_ERR(clock)) {
 +                      dev_err(&fmd->pdev->dev, "Failed to get clock: %s\n",
 +                                                              clk_name);
 +                      ret = PTR_ERR(clock);
 +                      break;
 +              }
 +              ret = clk_prepare(clock);
 +              if (ret < 0) {
 +                      clk_put(clock);
 +                      fmd->camclk[i].clock = ERR_PTR(-EINVAL);
 +                      break;
 +              }
 +              fmd->camclk[i].clock = clock;
 +      }
 +      if (ret)
 +              fimc_md_put_clocks(fmd);
 +
 +      if (!fmd->use_isp)
 +              return 0;
 +      /*
 +       * For now get only PIXELASYNCM1 clock (Writeback B/ISP),
 +       * leave PIXELASYNCM0 out for the LCD Writeback driver.
 +       */
 +      fmd->wbclk[CLK_IDX_WB_A] = ERR_PTR(-EINVAL);
 +
 +      for (i = CLK_IDX_WB_B; i < FIMC_MAX_WBCLKS; i++) {
 +              snprintf(clk_name, sizeof(clk_name), "pxl_async%u", i);
 +              clock = clk_get(dev, clk_name);
 +              if (IS_ERR(clock)) {
 +                      v4l2_err(&fmd->v4l2_dev, "Failed to get clock: %s\n",
 +                                clk_name);
 +                      ret = PTR_ERR(clock);
 +                      break;
 +              }
 +              fmd->wbclk[i] = clock;
 +      }
 +      if (ret)
 +              fimc_md_put_clocks(fmd);
 +
 +      return ret;
 +}
 +
 +static int __fimc_md_set_camclk(struct fimc_md *fmd,
 +                              struct fimc_sensor_info *s_info,
 +                              bool on)
 +{
 +      struct fimc_source_info *pdata = &s_info->pdata;
 +      struct fimc_camclk_info *camclk;
 +      int ret = 0;
 +
 +      if (WARN_ON(pdata->clk_id >= FIMC_MAX_CAMCLKS) || !fmd || !fmd->pmf)
 +              return -EINVAL;
 +
 +      camclk = &fmd->camclk[pdata->clk_id];
 +
 +      dbg("camclk %d, f: %lu, use_count: %d, on: %d",
 +          pdata->clk_id, pdata->clk_frequency, camclk->use_count, on);
 +
 +      if (on) {
 +              if (camclk->use_count > 0 &&
 +                  camclk->frequency != pdata->clk_frequency)
 +                      return -EINVAL;
 +
 +              if (camclk->use_count++ == 0) {
 +                      clk_set_rate(camclk->clock, pdata->clk_frequency);
 +                      camclk->frequency = pdata->clk_frequency;
 +                      ret = pm_runtime_get_sync(fmd->pmf);
 +                      if (ret < 0)
 +                              return ret;
 +                      ret = clk_enable(camclk->clock);
 +                      dbg("Enabled camclk %d: f: %lu", pdata->clk_id,
 +                          clk_get_rate(camclk->clock));
 +              }
 +              return ret;
 +      }
 +
 +      if (WARN_ON(camclk->use_count == 0))
 +              return 0;
 +
 +      if (--camclk->use_count == 0) {
 +              clk_disable(camclk->clock);
 +              pm_runtime_put(fmd->pmf);
 +              dbg("Disabled camclk %d", pdata->clk_id);
 +      }
 +      return ret;
 +}
 +
 +/**
 + * fimc_md_set_camclk - peripheral sensor clock setup
 + * @sd: sensor subdev to configure sclk_cam clock for
 + * @on: 1 to enable or 0 to disable the clock
 + *
 + * There are 2 separate clock outputs available in the SoC for external
 + * image processors. These clocks are shared between all registered FIMC
 + * devices to which sensors can be attached, either directly or through
 + * the MIPI CSI receiver. The clock is allowed here to be used by
 + * multiple sensors concurrently if they use same frequency.
 + * This function should only be called when the graph mutex is held.
 + */
 +int fimc_md_set_camclk(struct v4l2_subdev *sd, bool on)
 +{
 +      struct fimc_sensor_info *s_info = v4l2_get_subdev_hostdata(sd);
 +      struct fimc_md *fmd = entity_to_fimc_mdev(&sd->entity);
 +
 +      return __fimc_md_set_camclk(fmd, s_info, on);
 +}
 +
 +static int fimc_md_link_notify(struct media_pad *source,
 +                             struct media_pad *sink, u32 flags)
 +{
 +      struct fimc_lite *fimc_lite = NULL;
 +      struct fimc_dev *fimc = NULL;
 +      struct fimc_pipeline *pipeline;
 +      struct v4l2_subdev *sd;
 +      struct mutex *lock;
-               int i;
-               mutex_lock(lock);
-               ret = __fimc_pipeline_close(pipeline);
++      int i, ret = 0;
 +      int ref_count;
 +
 +      if (media_entity_type(sink->entity) != MEDIA_ENT_T_V4L2_SUBDEV)
 +              return 0;
 +
 +      sd = media_entity_to_v4l2_subdev(sink->entity);
 +
 +      switch (sd->grp_id) {
 +      case GRP_ID_FLITE:
 +              fimc_lite = v4l2_get_subdevdata(sd);
 +              if (WARN_ON(fimc_lite == NULL))
 +                      return 0;
 +              pipeline = &fimc_lite->pipeline;
 +              lock = &fimc_lite->lock;
 +              break;
 +      case GRP_ID_FIMC:
 +              fimc = v4l2_get_subdevdata(sd);
 +              if (WARN_ON(fimc == NULL))
 +                      return 0;
 +              pipeline = &fimc->pipeline;
 +              lock = &fimc->lock;
 +              break;
 +      default:
 +              return 0;
 +      }
 +
++      mutex_lock(lock);
++      ref_count = fimc ? fimc->vid_cap.refcnt : fimc_lite->ref_count;
++
 +      if (!(flags & MEDIA_LNK_FL_ENABLED)) {
-               if (fimc)
-                       fimc_ctrls_delete(fimc->vid_cap.ctx);
-               mutex_unlock(lock);
-               return ret;
++              if (ref_count > 0) {
++                      ret = __fimc_pipeline_close(pipeline);
++                      if (!ret && fimc)
++                              fimc_ctrls_delete(fimc->vid_cap.ctx);
++              }
 +              for (i = 0; i < IDX_MAX; i++)
 +                      pipeline->subdevs[i] = NULL;
-       /*
-        * Link activation. Enable power of pipeline elements only if the
-        * pipeline is already in use, i.e. its video node is opened.
-        * Recreate the controls destroyed during the link deactivation.
-        */
-       mutex_lock(lock);
-       ref_count = fimc ? fimc->vid_cap.refcnt : fimc_lite->ref_count;
-       if (ref_count > 0)
-               ret = __fimc_pipeline_open(pipeline, source->entity, true);
-       if (!ret && fimc)
-               ret = fimc_capture_ctrls_create(fimc);
++      } else if (ref_count > 0) {
++              /*
++               * Link activation. Enable power of pipeline elements only if
++               * the pipeline is already in use, i.e. its video node is open.
++               * Recreate the controls destroyed during the link deactivation.
++               */
++              ret = __fimc_pipeline_open(pipeline,
++                                         source->entity, true);
++              if (!ret && fimc)
++                      ret = fimc_capture_ctrls_create(fimc);
 +      }
 +
 +      mutex_unlock(lock);
 +      return ret ? -EPIPE : ret;
 +}
 +
 +static ssize_t fimc_md_sysfs_show(struct device *dev,
 +                                struct device_attribute *attr, char *buf)
 +{
 +      struct platform_device *pdev = to_platform_device(dev);
 +      struct fimc_md *fmd = platform_get_drvdata(pdev);
 +
 +      if (fmd->user_subdev_api)
 +              return strlcpy(buf, "Sub-device API (sub-dev)\n", PAGE_SIZE);
 +
 +      return strlcpy(buf, "V4L2 video node only API (vid-dev)\n", PAGE_SIZE);
 +}
 +
 +static ssize_t fimc_md_sysfs_store(struct device *dev,
 +                                 struct device_attribute *attr,
 +                                 const char *buf, size_t count)
 +{
 +      struct platform_device *pdev = to_platform_device(dev);
 +      struct fimc_md *fmd = platform_get_drvdata(pdev);
 +      bool subdev_api;
 +      int i;
 +
 +      if (!strcmp(buf, "vid-dev\n"))
 +              subdev_api = false;
 +      else if (!strcmp(buf, "sub-dev\n"))
 +              subdev_api = true;
 +      else
 +              return count;
 +
 +      fmd->user_subdev_api = subdev_api;
 +      for (i = 0; i < FIMC_MAX_DEVS; i++)
 +              if (fmd->fimc[i])
 +                      fmd->fimc[i]->vid_cap.user_subdev_api = subdev_api;
 +      return count;
 +}
 +/*
 + * This device attribute is to select video pipeline configuration method.
 + * There are following valid values:
 + *  vid-dev - for V4L2 video node API only, subdevice will be configured
 + *  by the host driver.
 + *  sub-dev - for media controller API, subdevs must be configured in user
 + *  space before starting streaming.
 + */
 +static DEVICE_ATTR(subdev_conf_mode, S_IWUSR | S_IRUGO,
 +                 fimc_md_sysfs_show, fimc_md_sysfs_store);
 +
 +static int fimc_md_get_pinctrl(struct fimc_md *fmd)
 +{
 +      struct device *dev = &fmd->pdev->dev;
 +      struct fimc_pinctrl *pctl = &fmd->pinctl;
 +
 +      pctl->pinctrl = devm_pinctrl_get(dev);
 +      if (IS_ERR(pctl->pinctrl))
 +              return PTR_ERR(pctl->pinctrl);
 +
 +      pctl->state_default = pinctrl_lookup_state(pctl->pinctrl,
 +                                      PINCTRL_STATE_DEFAULT);
 +      if (IS_ERR(pctl->state_default))
 +              return PTR_ERR(pctl->state_default);
 +
 +      pctl->state_idle = pinctrl_lookup_state(pctl->pinctrl,
 +                                      PINCTRL_STATE_IDLE);
 +      return 0;
 +}
 +
 +static int fimc_md_probe(struct platform_device *pdev)
 +{
 +      struct device *dev = &pdev->dev;
 +      struct v4l2_device *v4l2_dev;
 +      struct fimc_md *fmd;
 +      int ret;
 +
 +      fmd = devm_kzalloc(dev, sizeof(*fmd), GFP_KERNEL);
 +      if (!fmd)
 +              return -ENOMEM;
 +
 +      spin_lock_init(&fmd->slock);
 +      fmd->pdev = pdev;
 +
 +      strlcpy(fmd->media_dev.model, "SAMSUNG S5P FIMC",
 +              sizeof(fmd->media_dev.model));
 +      fmd->media_dev.link_notify = fimc_md_link_notify;
 +      fmd->media_dev.dev = dev;
 +
 +      v4l2_dev = &fmd->v4l2_dev;
 +      v4l2_dev->mdev = &fmd->media_dev;
 +      v4l2_dev->notify = fimc_sensor_notify;
 +      strlcpy(v4l2_dev->name, "s5p-fimc-md", sizeof(v4l2_dev->name));
 +
 +      ret = v4l2_device_register(dev, &fmd->v4l2_dev);
 +      if (ret < 0) {
 +              v4l2_err(v4l2_dev, "Failed to register v4l2_device: %d\n", ret);
 +              return ret;
 +      }
 +      ret = media_device_register(&fmd->media_dev);
 +      if (ret < 0) {
 +              v4l2_err(v4l2_dev, "Failed to register media device: %d\n", ret);
 +              goto err_md;
 +      }
 +      ret = fimc_md_get_clocks(fmd);
 +      if (ret)
 +              goto err_clk;
 +
 +      fmd->user_subdev_api = (dev->of_node != NULL);
 +
 +      /* Protect the media graph while we're registering entities */
 +      mutex_lock(&fmd->media_dev.graph_mutex);
 +
 +      ret = fimc_md_get_pinctrl(fmd);
 +      if (ret < 0) {
 +              if (ret != EPROBE_DEFER)
 +                      dev_err(dev, "Failed to get pinctrl: %d\n", ret);
 +              goto err_unlock;
 +      }
 +
 +      if (dev->of_node)
 +              ret = fimc_md_register_of_platform_entities(fmd, dev->of_node);
 +      else
 +              ret = bus_for_each_dev(&platform_bus_type, NULL, fmd,
 +                                              fimc_md_pdev_match);
 +      if (ret)
 +              goto err_unlock;
 +
 +      if (dev->platform_data || dev->of_node) {
 +              ret = fimc_md_register_sensor_entities(fmd);
 +              if (ret)
 +                      goto err_unlock;
 +      }
 +
 +      ret = fimc_md_create_links(fmd);
 +      if (ret)
 +              goto err_unlock;
 +      ret = v4l2_device_register_subdev_nodes(&fmd->v4l2_dev);
 +      if (ret)
 +              goto err_unlock;
 +
 +      ret = device_create_file(&pdev->dev, &dev_attr_subdev_conf_mode);
 +      if (ret)
 +              goto err_unlock;
 +
 +      platform_set_drvdata(pdev, fmd);
 +      mutex_unlock(&fmd->media_dev.graph_mutex);
 +      return 0;
 +
 +err_unlock:
 +      mutex_unlock(&fmd->media_dev.graph_mutex);
 +err_clk:
 +      media_device_unregister(&fmd->media_dev);
 +      fimc_md_put_clocks(fmd);
 +      fimc_md_unregister_entities(fmd);
 +err_md:
 +      v4l2_device_unregister(&fmd->v4l2_dev);
 +      return ret;
 +}
 +
 +static int fimc_md_remove(struct platform_device *pdev)
 +{
 +      struct fimc_md *fmd = platform_get_drvdata(pdev);
 +
 +      if (!fmd)
 +              return 0;
 +      device_remove_file(&pdev->dev, &dev_attr_subdev_conf_mode);
 +      fimc_md_unregister_entities(fmd);
 +      media_device_unregister(&fmd->media_dev);
 +      fimc_md_put_clocks(fmd);
 +      return 0;
 +}
 +
 +static struct platform_device_id fimc_driver_ids[] __always_unused = {
 +      { .name = "s5p-fimc-md" },
 +      { },
 +};
 +MODULE_DEVICE_TABLE(platform, fimc_driver_ids);
 +
 +static const struct of_device_id fimc_md_of_match[] = {
 +      { .compatible = "samsung,fimc" },
 +      { },
 +};
 +MODULE_DEVICE_TABLE(of, fimc_md_of_match);
 +
 +static struct platform_driver fimc_md_driver = {
 +      .probe          = fimc_md_probe,
 +      .remove         = fimc_md_remove,
 +      .driver = {
 +              .of_match_table = of_match_ptr(fimc_md_of_match),
 +              .name           = "s5p-fimc-md",
 +              .owner          = THIS_MODULE,
 +      }
 +};
 +
 +static int __init fimc_md_init(void)
 +{
 +      int ret;
 +
 +      request_module("s5p-csis");
 +      ret = fimc_register_driver();
 +      if (ret)
 +              return ret;
 +
 +      return platform_driver_register(&fimc_md_driver);
 +}
 +
 +static void __exit fimc_md_exit(void)
 +{
 +      platform_driver_unregister(&fimc_md_driver);
 +      fimc_unregister_driver();
 +}
 +
 +module_init(fimc_md_init);
 +module_exit(fimc_md_exit);
 +
 +MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
 +MODULE_DESCRIPTION("S5P FIMC camera host interface/video postprocessor driver");
 +MODULE_LICENSE("GPL");
 +MODULE_VERSION("2.0.1");
index 00c4a1993d587fccc24297cc488ebd6d17ed4efd,768aaf62d5dc300a7b2d7e150265e27035ef15cb..aa50c46314b7456cdc43c565af2d52b5804f26eb
@@@ -9,11 -9,8 +9,11 @@@ videodev-objs  :=      v4l2-dev.o v4l2-ioctl.
  ifeq ($(CONFIG_COMPAT),y)
    videodev-objs += v4l2-compat-ioctl32.o
  endif
 +ifeq ($(CONFIG_OF),y)
 +  videodev-objs += v4l2-of.o
 +endif
  
- obj-$(CONFIG_VIDEO_DEV) += videodev.o
+ obj-$(CONFIG_VIDEO_V4L2) += videodev.o
  obj-$(CONFIG_VIDEO_V4L2_INT_DEVICE) += v4l2-int-device.o
  obj-$(CONFIG_VIDEO_V4L2) += v4l2-common.o
  
Simple merge