All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC v4 0/8] Renesas CEU driver
@ 2017-05-19 16:02 Jacopo Mondi
  2017-05-19 16:02 ` [RFC v4 1/8] include: media: Move and update CEU driver interface Jacopo Mondi
                   ` (7 more replies)
  0 siblings, 8 replies; 10+ messages in thread
From: Jacopo Mondi @ 2017-05-19 16:02 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm; +Cc: linux-renesas-soc

Hello Laurent and Renesas list,
    this is new version of Capture Engine Unit driver.

Compared to "RFC v3", still in review, patches [02/08] and [03/08] are
un-changed. I have added changes to the newly developed CEU driver in a
separate patch ([04/08) to ease the review process.

Not having modified the CEU driver sent in "RFC v3", refer to that series for
a more detailed description of patches [02/08] and [03/08].

In this series I have added modification to Migo-R startup code to use the new
CE driver with the two sensors installed on that board. Both sensor drivers were
soc_camera dependent, and I have removed that dependency in patches [06/08] and
[07/08]. The drivers have not been moved yet from soc_camera sub-directory not
to break other platforms.

Patch [05/08] is a fix required to have ov772x probe properly, and has already
been sent to linux-media as stand-alone fix.

Series is in RFC as I have not tested image capture yet, nor I have update
other sh platforms to use the new CEU header, introduced with patch [01/08].

On Migo-R I have verified the sensors probe correctly, and a video device
gets registered after both async sensor notification have been received by
the CEU driver.

ov772x 0-0021: ov7725 Product ID 77:21 Manufacturer ID 7f:a2
tw9910 0-0045: tw9910 Product ID b:0
renesas-ceu renesas-ceu.0: Video device registered

Next step is to test image capture and possibly input selection on Migo-R
platform.

Thanks
   j


Jacopo Mondi (8):
  include: media: Move and update CEU driver interface
  media: platform: soc-camera: Remove SH CEU driver
  media: platform: Add Renesas CEU driver
  media: platform: ceu: Support for multiple subdevs
  media: i2c: ov772x: Force use of SCCB protocol
  media: i2c: ov772x: Remove soc_camera dependencies
  media: i2c: tw9910: Remove soc_camera dependencies
  arch: sh: migor: Use new CEU camera driver

 arch/sh/boards/mach-migor/setup.c                  |  126 +-
 drivers/media/i2c/soc_camera/ov772x.c              |   96 +-
 drivers/media/i2c/soc_camera/tw9910.c              |   68 +-
 drivers/media/platform/Kconfig                     |    8 +
 drivers/media/platform/Makefile                    |    2 +
 drivers/media/platform/renesas-ceu.c               | 1669 ++++++++++++++++++
 drivers/media/platform/soc_camera/Kconfig          |   10 -
 drivers/media/platform/soc_camera/Makefile         |    1 -
 .../platform/soc_camera/sh_mobile_ceu_camera.c     | 1821 --------------------
 include/media/drv-intf/renesas_ceu.h               |   28 +
 include/media/drv-intf/sh_mobile_ceu.h             |   28 -
 include/media/i2c/ov772x.h                         |    3 +
 include/media/i2c/tw9910.h                         |    6 +
 13 files changed, 1878 insertions(+), 1988 deletions(-)
 create mode 100644 drivers/media/platform/renesas-ceu.c
 delete mode 100644 drivers/media/platform/soc_camera/sh_mobile_ceu_camera.c
 create mode 100644 include/media/drv-intf/renesas_ceu.h
 delete mode 100644 include/media/drv-intf/sh_mobile_ceu.h

--
2.7.4

^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC v4 1/8] include: media: Move and update CEU driver interface
  2017-05-19 16:02 [RFC v4 0/8] Renesas CEU driver Jacopo Mondi
@ 2017-05-19 16:02 ` Jacopo Mondi
  2017-05-20 11:59   ` Laurent Pinchart
  2017-05-19 16:02 ` [RFC v4 2/8] media: platform: soc-camera: Remove SH CEU driver Jacopo Mondi
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 10+ messages in thread
From: Jacopo Mondi @ 2017-05-19 16:02 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm; +Cc: linux-renesas-soc

Update Renesas CEU driver interface to use driver interface
SQUASH

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
---
 include/media/drv-intf/renesas_ceu.h   | 28 ++++++++++++++++++++++++++++
 include/media/drv-intf/sh_mobile_ceu.h | 28 ----------------------------
 2 files changed, 28 insertions(+), 28 deletions(-)
 create mode 100644 include/media/drv-intf/renesas_ceu.h
 delete mode 100644 include/media/drv-intf/sh_mobile_ceu.h

diff --git a/include/media/drv-intf/renesas_ceu.h b/include/media/drv-intf/renesas_ceu.h
new file mode 100644
index 0000000..291661d
--- /dev/null
+++ b/include/media/drv-intf/renesas_ceu.h
@@ -0,0 +1,28 @@
+#ifndef __ASM_RENESAS_CEU_H__
+#define __ASM_RENESAS_CEU_H__
+
+#define CEU_FLAG_USE_8BIT_BUS   (1 << 0) /* use  8bit bus width */
+#define CEU_FLAG_USE_16BIT_BUS  (1 << 1) /* use 16bit bus width */
+#define CEU_FLAG_HSYNC_LOW      (1 << 2) /* default High if possible */
+#define CEU_FLAG_VSYNC_LOW      (1 << 3)
+#define CEU_FLAG_LOWER_8BIT     (1 << 4) /* default upper 8bit */
+#define CEU_FLAG_PRIMARY_SENS	(1 << 5) /* sensor is primary */
+
+#define CEU_MAX_SENS		2
+
+struct device;
+struct resource;
+
+struct ceu_async_subdev {
+	unsigned long flags;
+	unsigned int i2c_adapter_id;
+	unsigned int i2c_address;
+
+};
+
+struct ceu_info {
+	unsigned int num_subdevs;
+	struct ceu_async_subdev subdevs[CEU_MAX_SENS];
+};
+
+#endif /* __ASM_RENESAS_CEU_H__ */
diff --git a/include/media/drv-intf/sh_mobile_ceu.h b/include/media/drv-intf/sh_mobile_ceu.h
deleted file mode 100644
index 2f43f7d..0000000
--- a/include/media/drv-intf/sh_mobile_ceu.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#ifndef __ASM_SH_MOBILE_CEU_H__
-#define __ASM_SH_MOBILE_CEU_H__
-
-#define SH_CEU_FLAG_USE_8BIT_BUS	(1 << 0) /* use  8bit bus width */
-#define SH_CEU_FLAG_USE_16BIT_BUS	(1 << 1) /* use 16bit bus width */
-#define SH_CEU_FLAG_HSYNC_LOW		(1 << 2) /* default High if possible */
-#define SH_CEU_FLAG_VSYNC_LOW		(1 << 3) /* default High if possible */
-#define SH_CEU_FLAG_LOWER_8BIT		(1 << 4) /* default upper 8bit */
-
-struct device;
-struct resource;
-
-struct sh_mobile_ceu_companion {
-	u32		num_resources;
-	struct resource	*resource;
-	int		id;
-	void		*platform_data;
-};
-
-struct sh_mobile_ceu_info {
-	unsigned long flags;
-	int max_width;
-	int max_height;
-	struct v4l2_async_subdev **asd;	/* Flat array, arranged in groups */
-	unsigned int *asd_sizes;	/* 0-terminated array pf asd group sizes */
-};
-
-#endif /* __ASM_SH_MOBILE_CEU_H__ */
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC v4 2/8] media: platform: soc-camera: Remove SH CEU driver
  2017-05-19 16:02 [RFC v4 0/8] Renesas CEU driver Jacopo Mondi
  2017-05-19 16:02 ` [RFC v4 1/8] include: media: Move and update CEU driver interface Jacopo Mondi
@ 2017-05-19 16:02 ` Jacopo Mondi
  2017-05-19 16:02 ` [RFC v4 3/8] media: platform: Add Renesas " Jacopo Mondi
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Jacopo Mondi @ 2017-05-19 16:02 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm; +Cc: linux-renesas-soc

Remove SH Mobile CEU driver from soc-camera framework

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
---
 drivers/media/platform/soc_camera/Kconfig          |   10 -
 drivers/media/platform/soc_camera/Makefile         |    1 -
 .../platform/soc_camera/sh_mobile_ceu_camera.c     | 1821 --------------------
 3 files changed, 1832 deletions(-)
 delete mode 100644 drivers/media/platform/soc_camera/sh_mobile_ceu_camera.c

diff --git a/drivers/media/platform/soc_camera/Kconfig b/drivers/media/platform/soc_camera/Kconfig
index 86d7478..99ee15e 100644
--- a/drivers/media/platform/soc_camera/Kconfig
+++ b/drivers/media/platform/soc_camera/Kconfig
@@ -17,16 +17,6 @@ config SOC_CAMERA_PLATFORM
 	help
 	  This is a generic SoC camera platform driver, useful for testing
 
-config VIDEO_SH_MOBILE_CEU
-	tristate "SuperH Mobile CEU Interface driver"
-	depends on VIDEO_DEV && SOC_CAMERA && HAS_DMA && HAVE_CLK
-	depends on ARCH_SHMOBILE || COMPILE_TEST
-	depends on HAS_DMA
-	select VIDEOBUF2_DMA_CONTIG
-	select SOC_CAMERA_SCALE_CROP
-	---help---
-	  This is a v4l2 driver for the SuperH Mobile CEU Interface
-
 config VIDEO_ATMEL_ISI
 	tristate "ATMEL Image Sensor Interface (ISI) support"
 	depends on VIDEO_DEV && SOC_CAMERA
diff --git a/drivers/media/platform/soc_camera/Makefile b/drivers/media/platform/soc_camera/Makefile
index 7633a0f..7ecb22b 100644
--- a/drivers/media/platform/soc_camera/Makefile
+++ b/drivers/media/platform/soc_camera/Makefile
@@ -7,4 +7,3 @@ obj-$(CONFIG_SOC_CAMERA_PLATFORM)	+= soc_camera_platform.o
 
 # soc-camera host drivers have to be linked after camera drivers
 obj-$(CONFIG_VIDEO_ATMEL_ISI)		+= atmel-isi.o
-obj-$(CONFIG_VIDEO_SH_MOBILE_CEU)	+= sh_mobile_ceu_camera.o
diff --git a/drivers/media/platform/soc_camera/sh_mobile_ceu_camera.c b/drivers/media/platform/soc_camera/sh_mobile_ceu_camera.c
deleted file mode 100644
index a15bfb5..0000000
--- a/drivers/media/platform/soc_camera/sh_mobile_ceu_camera.c
+++ /dev/null
@@ -1,1821 +0,0 @@
-/*
- * V4L2 Driver for SuperH Mobile CEU interface
- *
- * Copyright (C) 2008 Magnus Damm
- *
- * Based on V4L2 Driver for PXA camera host - "pxa_camera.c",
- *
- * Copyright (C) 2006, Sascha Hauer, Pengutronix
- * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de>
- *
- * 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/init.h>
-#include <linux/module.h>
-#include <linux/io.h>
-#include <linux/completion.h>
-#include <linux/delay.h>
-#include <linux/dma-mapping.h>
-#include <linux/err.h>
-#include <linux/errno.h>
-#include <linux/fs.h>
-#include <linux/interrupt.h>
-#include <linux/kernel.h>
-#include <linux/mm.h>
-#include <linux/moduleparam.h>
-#include <linux/of.h>
-#include <linux/time.h>
-#include <linux/slab.h>
-#include <linux/device.h>
-#include <linux/platform_device.h>
-#include <linux/videodev2.h>
-#include <linux/pm_runtime.h>
-#include <linux/sched.h>
-
-#include <media/v4l2-async.h>
-#include <media/v4l2-common.h>
-#include <media/v4l2-dev.h>
-#include <media/soc_camera.h>
-#include <media/drv-intf/sh_mobile_ceu.h>
-#include <media/videobuf2-dma-contig.h>
-#include <media/v4l2-mediabus.h>
-#include <media/drv-intf/soc_mediabus.h>
-
-#include "soc_scale_crop.h"
-
-/* register offsets for sh7722 / sh7723 */
-
-#define CAPSR  0x00 /* Capture start register */
-#define CAPCR  0x04 /* Capture control register */
-#define CAMCR  0x08 /* Capture interface control register */
-#define CMCYR  0x0c /* Capture interface cycle  register */
-#define CAMOR  0x10 /* Capture interface offset register */
-#define CAPWR  0x14 /* Capture interface width register */
-#define CAIFR  0x18 /* Capture interface input format register */
-#define CSTCR  0x20 /* Camera strobe control register (<= sh7722) */
-#define CSECR  0x24 /* Camera strobe emission count register (<= sh7722) */
-#define CRCNTR 0x28 /* CEU register control register */
-#define CRCMPR 0x2c /* CEU register forcible control register */
-#define CFLCR  0x30 /* Capture filter control register */
-#define CFSZR  0x34 /* Capture filter size clip register */
-#define CDWDR  0x38 /* Capture destination width register */
-#define CDAYR  0x3c /* Capture data address Y register */
-#define CDACR  0x40 /* Capture data address C register */
-#define CDBYR  0x44 /* Capture data bottom-field address Y register */
-#define CDBCR  0x48 /* Capture data bottom-field address C register */
-#define CBDSR  0x4c /* Capture bundle destination size register */
-#define CFWCR  0x5c /* Firewall operation control register */
-#define CLFCR  0x60 /* Capture low-pass filter control register */
-#define CDOCR  0x64 /* Capture data output control register */
-#define CDDCR  0x68 /* Capture data complexity level register */
-#define CDDAR  0x6c /* Capture data complexity level address register */
-#define CEIER  0x70 /* Capture event interrupt enable register */
-#define CETCR  0x74 /* Capture event flag clear register */
-#define CSTSR  0x7c /* Capture status register */
-#define CSRTR  0x80 /* Capture software reset register */
-#define CDSSR  0x84 /* Capture data size register */
-#define CDAYR2 0x90 /* Capture data address Y register 2 */
-#define CDACR2 0x94 /* Capture data address C register 2 */
-#define CDBYR2 0x98 /* Capture data bottom-field address Y register 2 */
-#define CDBCR2 0x9c /* Capture data bottom-field address C register 2 */
-
-#undef DEBUG_GEOMETRY
-#ifdef DEBUG_GEOMETRY
-#define dev_geo	dev_info
-#else
-#define dev_geo	dev_dbg
-#endif
-
-/* per video frame buffer */
-struct sh_mobile_ceu_buffer {
-	struct vb2_v4l2_buffer vb; /* v4l buffer must be first */
-	struct list_head queue;
-};
-
-struct sh_mobile_ceu_dev {
-	struct soc_camera_host ici;
-
-	unsigned int irq;
-	void __iomem *base;
-	size_t video_limit;
-	size_t buf_total;
-
-	spinlock_t lock;		/* Protects video buffer lists */
-	struct list_head capture;
-	struct vb2_v4l2_buffer *active;
-
-	struct sh_mobile_ceu_info *pdata;
-	struct completion complete;
-
-	u32 cflcr;
-
-	/* static max sizes either from platform data or default */
-	int max_width;
-	int max_height;
-
-	enum v4l2_field field;
-	int sequence;
-	unsigned long flags;
-
-	unsigned int image_mode:1;
-	unsigned int is_16bit:1;
-	unsigned int frozen:1;
-};
-
-struct sh_mobile_ceu_cam {
-	/* CEU offsets within the camera output, before the CEU scaler */
-	unsigned int ceu_left;
-	unsigned int ceu_top;
-	/* Client output, as seen by the CEU */
-	unsigned int width;
-	unsigned int height;
-	/*
-	 * User window from S_SELECTION / G_SELECTION, produced by client cropping and
-	 * scaling, CEU scaling and CEU cropping, mapped back onto the client
-	 * input window
-	 */
-	struct v4l2_rect subrect;
-	/* Camera cropping rectangle */
-	struct v4l2_rect rect;
-	const struct soc_mbus_pixelfmt *extra_fmt;
-	u32 code;
-};
-
-static struct sh_mobile_ceu_buffer *to_ceu_vb(struct vb2_v4l2_buffer *vbuf)
-{
-	return container_of(vbuf, struct sh_mobile_ceu_buffer, vb);
-}
-
-static void ceu_write(struct sh_mobile_ceu_dev *priv,
-		      unsigned long reg_offs, u32 data)
-{
-	iowrite32(data, priv->base + reg_offs);
-}
-
-static u32 ceu_read(struct sh_mobile_ceu_dev *priv, unsigned long reg_offs)
-{
-	return ioread32(priv->base + reg_offs);
-}
-
-static int sh_mobile_ceu_soft_reset(struct sh_mobile_ceu_dev *pcdev)
-{
-	int i, success = 0;
-
-	ceu_write(pcdev, CAPSR, 1 << 16); /* reset */
-
-	/* wait CSTSR.CPTON bit */
-	for (i = 0; i < 1000; i++) {
-		if (!(ceu_read(pcdev, CSTSR) & 1)) {
-			success++;
-			break;
-		}
-		udelay(1);
-	}
-
-	/* wait CAPSR.CPKIL bit */
-	for (i = 0; i < 1000; i++) {
-		if (!(ceu_read(pcdev, CAPSR) & (1 << 16))) {
-			success++;
-			break;
-		}
-		udelay(1);
-	}
-
-	if (2 != success) {
-		dev_warn(pcdev->ici.v4l2_dev.dev, "soft reset time out\n");
-		return -EIO;
-	}
-
-	return 0;
-}
-
-/*
- *  Videobuf operations
- */
-
-/*
- * .queue_setup() is called to check, whether the driver can accept the
- *		  requested number of buffers and to fill in plane sizes
- *		  for the current frame format if required
- */
-static int sh_mobile_ceu_videobuf_setup(struct vb2_queue *vq,
-			unsigned int *count, unsigned int *num_planes,
-			unsigned int sizes[], struct device *alloc_devs[])
-{
-	struct soc_camera_device *icd = soc_camera_from_vb2q(vq);
-	struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-
-	if (!vq->num_buffers)
-		pcdev->sequence = 0;
-
-	if (!*count)
-		*count = 2;
-
-	/* Called from VIDIOC_REQBUFS or in compatibility mode */
-	if (!*num_planes)
-		sizes[0] = icd->sizeimage;
-	else if (sizes[0] < icd->sizeimage)
-		return -EINVAL;
-
-	/* If *num_planes != 0, we have already verified *count. */
-	if (pcdev->video_limit) {
-		size_t size = PAGE_ALIGN(sizes[0]) * *count;
-
-		if (size + pcdev->buf_total > pcdev->video_limit)
-			*count = (pcdev->video_limit - pcdev->buf_total) /
-				PAGE_ALIGN(sizes[0]);
-	}
-
-	*num_planes = 1;
-
-	dev_dbg(icd->parent, "count=%d, size=%u\n", *count, sizes[0]);
-
-	return 0;
-}
-
-#define CEU_CETCR_MAGIC 0x0317f313 /* acknowledge magical interrupt sources */
-#define CEU_CETCR_IGRW (1 << 4) /* prohibited register access interrupt bit */
-#define CEU_CEIER_CPEIE (1 << 0) /* one-frame capture end interrupt */
-#define CEU_CEIER_VBP   (1 << 20) /* vbp error */
-#define CEU_CAPCR_CTNCP (1 << 16) /* continuous capture mode (if set) */
-#define CEU_CEIER_MASK (CEU_CEIER_CPEIE | CEU_CEIER_VBP)
-
-
-/*
- * return value doesn't reflex the success/failure to queue the new buffer,
- * but rather the status of the previous buffer.
- */
-static int sh_mobile_ceu_capture(struct sh_mobile_ceu_dev *pcdev)
-{
-	struct soc_camera_device *icd = pcdev->ici.icd;
-	dma_addr_t phys_addr_top, phys_addr_bottom;
-	unsigned long top1, top2;
-	unsigned long bottom1, bottom2;
-	u32 status;
-	bool planar;
-	int ret = 0;
-
-	/*
-	 * The hardware is _very_ picky about this sequence. Especially
-	 * the CEU_CETCR_MAGIC value. It seems like we need to acknowledge
-	 * several not-so-well documented interrupt sources in CETCR.
-	 */
-	ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) & ~CEU_CEIER_MASK);
-	status = ceu_read(pcdev, CETCR);
-	ceu_write(pcdev, CETCR, ~status & CEU_CETCR_MAGIC);
-	if (!pcdev->frozen)
-		ceu_write(pcdev, CEIER, ceu_read(pcdev, CEIER) | CEU_CEIER_MASK);
-	ceu_write(pcdev, CAPCR, ceu_read(pcdev, CAPCR) & ~CEU_CAPCR_CTNCP);
-	ceu_write(pcdev, CETCR, CEU_CETCR_MAGIC ^ CEU_CETCR_IGRW);
-
-	/*
-	 * When a VBP interrupt occurs, a capture end interrupt does not occur
-	 * and the image of that frame is not captured correctly. So, soft reset
-	 * is needed here.
-	 */
-	if (status & CEU_CEIER_VBP) {
-		sh_mobile_ceu_soft_reset(pcdev);
-		ret = -EIO;
-	}
-
-	if (pcdev->frozen) {
-		complete(&pcdev->complete);
-		return ret;
-	}
-
-	if (!pcdev->active)
-		return ret;
-
-	if (V4L2_FIELD_INTERLACED_BT == pcdev->field) {
-		top1	= CDBYR;
-		top2	= CDBCR;
-		bottom1	= CDAYR;
-		bottom2	= CDACR;
-	} else {
-		top1	= CDAYR;
-		top2	= CDACR;
-		bottom1	= CDBYR;
-		bottom2	= CDBCR;
-	}
-
-	phys_addr_top =
-		vb2_dma_contig_plane_dma_addr(&pcdev->active->vb2_buf, 0);
-
-	switch (icd->current_fmt->host_fmt->fourcc) {
-	case V4L2_PIX_FMT_NV12:
-	case V4L2_PIX_FMT_NV21:
-	case V4L2_PIX_FMT_NV16:
-	case V4L2_PIX_FMT_NV61:
-		planar = true;
-		break;
-	default:
-		planar = false;
-	}
-
-	ceu_write(pcdev, top1, phys_addr_top);
-	if (V4L2_FIELD_NONE != pcdev->field) {
-		phys_addr_bottom = phys_addr_top + icd->bytesperline;
-		ceu_write(pcdev, bottom1, phys_addr_bottom);
-	}
-
-	if (planar) {
-		phys_addr_top += icd->bytesperline * icd->user_height;
-		ceu_write(pcdev, top2, phys_addr_top);
-		if (V4L2_FIELD_NONE != pcdev->field) {
-			phys_addr_bottom = phys_addr_top + icd->bytesperline;
-			ceu_write(pcdev, bottom2, phys_addr_bottom);
-		}
-	}
-
-	ceu_write(pcdev, CAPSR, 0x1); /* start capture */
-
-	return ret;
-}
-
-static int sh_mobile_ceu_videobuf_prepare(struct vb2_buffer *vb)
-{
-	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
-	struct sh_mobile_ceu_buffer *buf = to_ceu_vb(vbuf);
-
-	/* Added list head initialization on alloc */
-	WARN(!list_empty(&buf->queue), "Buffer %p on queue!\n", vb);
-
-	return 0;
-}
-
-static void sh_mobile_ceu_videobuf_queue(struct vb2_buffer *vb)
-{
-	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
-	struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue);
-	struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-	struct sh_mobile_ceu_buffer *buf = to_ceu_vb(vbuf);
-	unsigned long size;
-
-	size = icd->sizeimage;
-
-	if (vb2_plane_size(vb, 0) < size) {
-		dev_err(icd->parent, "Buffer #%d too small (%lu < %lu)\n",
-			vb->index, vb2_plane_size(vb, 0), size);
-		goto error;
-	}
-
-	vb2_set_plane_payload(vb, 0, size);
-
-	dev_dbg(icd->parent, "%s (vb=0x%p) 0x%p %lu\n", __func__,
-		vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0));
-
-#ifdef DEBUG
-	/*
-	 * This can be useful if you want to see if we actually fill
-	 * the buffer with something
-	 */
-	if (vb2_plane_vaddr(vb, 0))
-		memset(vb2_plane_vaddr(vb, 0), 0xaa, vb2_get_plane_payload(vb, 0));
-#endif
-
-	spin_lock_irq(&pcdev->lock);
-	list_add_tail(&buf->queue, &pcdev->capture);
-
-	if (!pcdev->active) {
-		/*
-		 * Because there were no active buffer at this moment,
-		 * we are not interested in the return value of
-		 * sh_mobile_ceu_capture here.
-		 */
-		pcdev->active = vbuf;
-		sh_mobile_ceu_capture(pcdev);
-	}
-	spin_unlock_irq(&pcdev->lock);
-
-	return;
-
-error:
-	vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
-}
-
-static void sh_mobile_ceu_videobuf_release(struct vb2_buffer *vb)
-{
-	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
-	struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue);
-	struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
-	struct sh_mobile_ceu_buffer *buf = to_ceu_vb(vbuf);
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-
-	spin_lock_irq(&pcdev->lock);
-
-	if (pcdev->active == vbuf) {
-		/* disable capture (release DMA buffer), reset */
-		ceu_write(pcdev, CAPSR, 1 << 16);
-		pcdev->active = NULL;
-	}
-
-	/*
-	 * Doesn't hurt also if the list is empty, but it hurts, if queuing the
-	 * buffer failed, and .buf_init() hasn't been called
-	 */
-	if (buf->queue.next)
-		list_del_init(&buf->queue);
-
-	pcdev->buf_total -= PAGE_ALIGN(vb2_plane_size(vb, 0));
-	dev_dbg(icd->parent, "%s() %zu bytes buffers\n", __func__,
-		pcdev->buf_total);
-
-	spin_unlock_irq(&pcdev->lock);
-}
-
-static int sh_mobile_ceu_videobuf_init(struct vb2_buffer *vb)
-{
-	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
-	struct soc_camera_device *icd = soc_camera_from_vb2q(vb->vb2_queue);
-	struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-
-	pcdev->buf_total += PAGE_ALIGN(vb2_plane_size(vb, 0));
-	dev_dbg(icd->parent, "%s() %zu bytes buffers\n", __func__,
-		pcdev->buf_total);
-
-	/* This is for locking debugging only */
-	INIT_LIST_HEAD(&to_ceu_vb(vbuf)->queue);
-	return 0;
-}
-
-static void sh_mobile_ceu_stop_streaming(struct vb2_queue *q)
-{
-	struct soc_camera_device *icd = soc_camera_from_vb2q(q);
-	struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-	struct list_head *buf_head, *tmp;
-
-	spin_lock_irq(&pcdev->lock);
-
-	pcdev->active = NULL;
-
-	list_for_each_safe(buf_head, tmp, &pcdev->capture)
-		list_del_init(buf_head);
-
-	spin_unlock_irq(&pcdev->lock);
-
-	sh_mobile_ceu_soft_reset(pcdev);
-}
-
-static const struct vb2_ops sh_mobile_ceu_videobuf_ops = {
-	.queue_setup	= sh_mobile_ceu_videobuf_setup,
-	.buf_prepare	= sh_mobile_ceu_videobuf_prepare,
-	.buf_queue	= sh_mobile_ceu_videobuf_queue,
-	.buf_cleanup	= sh_mobile_ceu_videobuf_release,
-	.buf_init	= sh_mobile_ceu_videobuf_init,
-	.wait_prepare	= vb2_ops_wait_prepare,
-	.wait_finish	= vb2_ops_wait_finish,
-	.stop_streaming	= sh_mobile_ceu_stop_streaming,
-};
-
-static irqreturn_t sh_mobile_ceu_irq(int irq, void *data)
-{
-	struct sh_mobile_ceu_dev *pcdev = data;
-	struct vb2_v4l2_buffer *vbuf;
-	int ret;
-
-	spin_lock(&pcdev->lock);
-
-	vbuf = pcdev->active;
-	if (!vbuf)
-		/* Stale interrupt from a released buffer */
-		goto out;
-
-	list_del_init(&to_ceu_vb(vbuf)->queue);
-
-	if (!list_empty(&pcdev->capture))
-		pcdev->active = &list_entry(pcdev->capture.next,
-					    struct sh_mobile_ceu_buffer, queue)->vb;
-	else
-		pcdev->active = NULL;
-
-	ret = sh_mobile_ceu_capture(pcdev);
-	vbuf->vb2_buf.timestamp = ktime_get_ns();
-	if (!ret) {
-		vbuf->field = pcdev->field;
-		vbuf->sequence = pcdev->sequence++;
-	}
-	vb2_buffer_done(&vbuf->vb2_buf,
-			ret < 0 ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
-
-out:
-	spin_unlock(&pcdev->lock);
-
-	return IRQ_HANDLED;
-}
-
-static int sh_mobile_ceu_add_device(struct soc_camera_device *icd)
-{
-	dev_info(icd->parent,
-		 "SuperH Mobile CEU driver attached to camera %d\n",
-		 icd->devnum);
-
-	return 0;
-}
-
-static void sh_mobile_ceu_remove_device(struct soc_camera_device *icd)
-{
-	dev_info(icd->parent,
-		 "SuperH Mobile CEU driver detached from camera %d\n",
-		 icd->devnum);
-}
-
-/* Called with .host_lock held */
-static int sh_mobile_ceu_clock_start(struct soc_camera_host *ici)
-{
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-
-	pm_runtime_get_sync(ici->v4l2_dev.dev);
-
-	pcdev->buf_total = 0;
-
-	sh_mobile_ceu_soft_reset(pcdev);
-
-	return 0;
-}
-
-/* Called with .host_lock held */
-static void sh_mobile_ceu_clock_stop(struct soc_camera_host *ici)
-{
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-
-	/* disable capture, disable interrupts */
-	ceu_write(pcdev, CEIER, 0);
-	sh_mobile_ceu_soft_reset(pcdev);
-
-	/* make sure active buffer is canceled */
-	spin_lock_irq(&pcdev->lock);
-	if (pcdev->active) {
-		list_del_init(&to_ceu_vb(pcdev->active)->queue);
-		vb2_buffer_done(&pcdev->active->vb2_buf, VB2_BUF_STATE_ERROR);
-		pcdev->active = NULL;
-	}
-	spin_unlock_irq(&pcdev->lock);
-
-	pm_runtime_put(ici->v4l2_dev.dev);
-}
-
-/*
- * See chapter 29.4.12 "Capture Filter Control Register (CFLCR)"
- * in SH7722 Hardware Manual
- */
-static unsigned int size_dst(unsigned int src, unsigned int scale)
-{
-	unsigned int mant_pre = scale >> 12;
-	if (!src || !scale)
-		return src;
-	return ((mant_pre + 2 * (src - 1)) / (2 * mant_pre) - 1) *
-		mant_pre * 4096 / scale + 1;
-}
-
-static u16 calc_scale(unsigned int src, unsigned int *dst)
-{
-	u16 scale;
-
-	if (src == *dst)
-		return 0;
-
-	scale = (src * 4096 / *dst) & ~7;
-
-	while (scale > 4096 && size_dst(src, scale) < *dst)
-		scale -= 8;
-
-	*dst = size_dst(src, scale);
-
-	return scale;
-}
-
-/* rect is guaranteed to not exceed the scaled camera rectangle */
-static void sh_mobile_ceu_set_rect(struct soc_camera_device *icd)
-{
-	struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
-	struct sh_mobile_ceu_cam *cam = icd->host_priv;
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-	unsigned int height, width, cdwdr_width, in_width, in_height;
-	unsigned int left_offset, top_offset;
-	u32 camor;
-
-	dev_geo(icd->parent, "Crop %ux%u@%u:%u\n",
-		icd->user_width, icd->user_height, cam->ceu_left, cam->ceu_top);
-
-	left_offset	= cam->ceu_left;
-	top_offset	= cam->ceu_top;
-
-	WARN_ON(icd->user_width & 3 || icd->user_height & 3);
-
-	width = icd->user_width;
-
-	if (pcdev->image_mode) {
-		in_width = cam->width;
-		if (!pcdev->is_16bit) {
-			in_width *= 2;
-			left_offset *= 2;
-		}
-	} else {
-		unsigned int w_factor;
-
-		switch (icd->current_fmt->host_fmt->packing) {
-		case SOC_MBUS_PACKING_2X8_PADHI:
-			w_factor = 2;
-			break;
-		default:
-			w_factor = 1;
-		}
-
-		in_width = cam->width * w_factor;
-		left_offset *= w_factor;
-	}
-
-	cdwdr_width = icd->bytesperline;
-
-	height = icd->user_height;
-	in_height = cam->height;
-	if (V4L2_FIELD_NONE != pcdev->field) {
-		height = (height / 2) & ~3;
-		in_height /= 2;
-		top_offset /= 2;
-		cdwdr_width *= 2;
-	}
-
-	/* Set CAMOR, CAPWR, CFSZR, take care of CDWDR */
-	camor = left_offset | (top_offset << 16);
-
-	dev_geo(icd->parent,
-		"CAMOR 0x%x, CAPWR 0x%x, CFSZR 0x%x, CDWDR 0x%x\n", camor,
-		(in_height << 16) | in_width, (height << 16) | width,
-		cdwdr_width);
-
-	ceu_write(pcdev, CAMOR, camor);
-	ceu_write(pcdev, CAPWR, (in_height << 16) | in_width);
-	/* CFSZR clipping is applied _after_ the scaling filter (CFLCR) */
-	ceu_write(pcdev, CFSZR, (height << 16) | width);
-	ceu_write(pcdev, CDWDR, cdwdr_width);
-}
-
-static u32 capture_save_reset(struct sh_mobile_ceu_dev *pcdev)
-{
-	u32 capsr = ceu_read(pcdev, CAPSR);
-	ceu_write(pcdev, CAPSR, 1 << 16); /* reset, stop capture */
-	return capsr;
-}
-
-static void capture_restore(struct sh_mobile_ceu_dev *pcdev, u32 capsr)
-{
-	unsigned long timeout = jiffies + 10 * HZ;
-
-	/*
-	 * Wait until the end of the current frame. It can take a long time,
-	 * but if it has been aborted by a CAPSR reset, it shoule exit sooner.
-	 */
-	while ((ceu_read(pcdev, CSTSR) & 1) && time_before(jiffies, timeout))
-		msleep(1);
-
-	if (time_after(jiffies, timeout)) {
-		dev_err(pcdev->ici.v4l2_dev.dev,
-			"Timeout waiting for frame end! Interface problem?\n");
-		return;
-	}
-
-	/* Wait until reset clears, this shall not hang... */
-	while (ceu_read(pcdev, CAPSR) & (1 << 16))
-		udelay(10);
-
-	/* Anything to restore? */
-	if (capsr & ~(1 << 16))
-		ceu_write(pcdev, CAPSR, capsr);
-}
-
-#define CEU_BUS_FLAGS (V4L2_MBUS_MASTER |	\
-		V4L2_MBUS_PCLK_SAMPLE_RISING |	\
-		V4L2_MBUS_HSYNC_ACTIVE_HIGH |	\
-		V4L2_MBUS_HSYNC_ACTIVE_LOW |	\
-		V4L2_MBUS_VSYNC_ACTIVE_HIGH |	\
-		V4L2_MBUS_VSYNC_ACTIVE_LOW |	\
-		V4L2_MBUS_DATA_ACTIVE_HIGH)
-
-/* Capture is not running, no interrupts, no locking needed */
-static int sh_mobile_ceu_set_bus_param(struct soc_camera_device *icd)
-{
-	struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
-	struct sh_mobile_ceu_cam *cam = icd->host_priv;
-	struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,};
-	unsigned long value, common_flags = CEU_BUS_FLAGS;
-	u32 capsr = capture_save_reset(pcdev);
-	unsigned int yuv_lineskip;
-	int ret;
-
-	/*
-	 * If the client doesn't implement g_mbus_config, we just use our
-	 * platform data
-	 */
-	ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg);
-	if (!ret) {
-		common_flags = soc_mbus_config_compatible(&cfg,
-							  common_flags);
-		if (!common_flags)
-			return -EINVAL;
-	} else if (ret != -ENOIOCTLCMD) {
-		return ret;
-	}
-
-	/* Make choises, based on platform preferences */
-	if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) &&
-	    (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) {
-		if (pcdev->flags & SH_CEU_FLAG_HSYNC_LOW)
-			common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH;
-		else
-			common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW;
-	}
-
-	if ((common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) &&
-	    (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) {
-		if (pcdev->flags & SH_CEU_FLAG_VSYNC_LOW)
-			common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_HIGH;
-		else
-			common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_LOW;
-	}
-
-	cfg.flags = common_flags;
-	ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg);
-	if (ret < 0 && ret != -ENOIOCTLCMD)
-		return ret;
-
-	if (icd->current_fmt->host_fmt->bits_per_sample > 8)
-		pcdev->is_16bit = 1;
-	else
-		pcdev->is_16bit = 0;
-
-	ceu_write(pcdev, CRCNTR, 0);
-	ceu_write(pcdev, CRCMPR, 0);
-
-	value = 0x00000010; /* data fetch by default */
-	yuv_lineskip = 0x10;
-
-	switch (icd->current_fmt->host_fmt->fourcc) {
-	case V4L2_PIX_FMT_NV12:
-	case V4L2_PIX_FMT_NV21:
-		/* convert 4:2:2 -> 4:2:0 */
-		yuv_lineskip = 0; /* skip for NV12/21, no skip for NV16/61 */
-		/* fall-through */
-	case V4L2_PIX_FMT_NV16:
-	case V4L2_PIX_FMT_NV61:
-		switch (cam->code) {
-		case MEDIA_BUS_FMT_UYVY8_2X8:
-			value = 0x00000000; /* Cb0, Y0, Cr0, Y1 */
-			break;
-		case MEDIA_BUS_FMT_VYUY8_2X8:
-			value = 0x00000100; /* Cr0, Y0, Cb0, Y1 */
-			break;
-		case MEDIA_BUS_FMT_YUYV8_2X8:
-			value = 0x00000200; /* Y0, Cb0, Y1, Cr0 */
-			break;
-		case MEDIA_BUS_FMT_YVYU8_2X8:
-			value = 0x00000300; /* Y0, Cr0, Y1, Cb0 */
-			break;
-		default:
-			BUG();
-		}
-	}
-
-	if (icd->current_fmt->host_fmt->fourcc == V4L2_PIX_FMT_NV21 ||
-	    icd->current_fmt->host_fmt->fourcc == V4L2_PIX_FMT_NV61)
-		value ^= 0x00000100; /* swap U, V to change from NV1x->NVx1 */
-
-	value |= common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW ? 1 << 1 : 0;
-	value |= common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW ? 1 << 0 : 0;
-
-	if (pcdev->is_16bit)
-		value |= 1 << 12;
-	else if (pcdev->flags & SH_CEU_FLAG_LOWER_8BIT)
-		value |= 2 << 12;
-
-	ceu_write(pcdev, CAMCR, value);
-
-	ceu_write(pcdev, CAPCR, 0x00300000);
-
-	switch (pcdev->field) {
-	case V4L2_FIELD_INTERLACED_TB:
-		value = 0x101;
-		break;
-	case V4L2_FIELD_INTERLACED_BT:
-		value = 0x102;
-		break;
-	default:
-		value = 0;
-		break;
-	}
-	ceu_write(pcdev, CAIFR, value);
-
-	sh_mobile_ceu_set_rect(icd);
-	mdelay(1);
-
-	dev_geo(icd->parent, "CFLCR 0x%x\n", pcdev->cflcr);
-	ceu_write(pcdev, CFLCR, pcdev->cflcr);
-
-	/*
-	 * A few words about byte order (observed in Big Endian mode)
-	 *
-	 * In data fetch mode bytes are received in chunks of 8 bytes.
-	 * D0, D1, D2, D3, D4, D5, D6, D7 (D0 received first)
-	 *
-	 * The data is however by default written to memory in reverse order:
-	 * D7, D6, D5, D4, D3, D2, D1, D0 (D7 written to lowest byte)
-	 *
-	 * The lowest three bits of CDOCR allows us to do swapping,
-	 * using 7 we swap the data bytes to match the incoming order:
-	 * D0, D1, D2, D3, D4, D5, D6, D7
-	 */
-	value = 0x00000007 | yuv_lineskip;
-
-	ceu_write(pcdev, CDOCR, value);
-	ceu_write(pcdev, CFWCR, 0); /* keep "datafetch firewall" disabled */
-
-	capture_restore(pcdev, capsr);
-
-	/* not in bundle mode: skip CBDSR, CDAYR2, CDACR2, CDBYR2, CDBCR2 */
-	return 0;
-}
-
-static int sh_mobile_ceu_try_bus_param(struct soc_camera_device *icd,
-				       unsigned char buswidth)
-{
-	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
-	unsigned long common_flags = CEU_BUS_FLAGS;
-	struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,};
-	int ret;
-
-	ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg);
-	if (!ret)
-		common_flags = soc_mbus_config_compatible(&cfg,
-							  common_flags);
-	else if (ret != -ENOIOCTLCMD)
-		return ret;
-
-	if (!common_flags || buswidth > 16)
-		return -EINVAL;
-
-	return 0;
-}
-
-static const struct soc_mbus_pixelfmt sh_mobile_ceu_formats[] = {
-	{
-		.fourcc			= V4L2_PIX_FMT_NV12,
-		.name			= "NV12",
-		.bits_per_sample	= 8,
-		.packing		= SOC_MBUS_PACKING_1_5X8,
-		.order			= SOC_MBUS_ORDER_LE,
-		.layout			= SOC_MBUS_LAYOUT_PLANAR_2Y_C,
-	}, {
-		.fourcc			= V4L2_PIX_FMT_NV21,
-		.name			= "NV21",
-		.bits_per_sample	= 8,
-		.packing		= SOC_MBUS_PACKING_1_5X8,
-		.order			= SOC_MBUS_ORDER_LE,
-		.layout			= SOC_MBUS_LAYOUT_PLANAR_2Y_C,
-	}, {
-		.fourcc			= V4L2_PIX_FMT_NV16,
-		.name			= "NV16",
-		.bits_per_sample	= 8,
-		.packing		= SOC_MBUS_PACKING_2X8_PADHI,
-		.order			= SOC_MBUS_ORDER_LE,
-		.layout			= SOC_MBUS_LAYOUT_PLANAR_Y_C,
-	}, {
-		.fourcc			= V4L2_PIX_FMT_NV61,
-		.name			= "NV61",
-		.bits_per_sample	= 8,
-		.packing		= SOC_MBUS_PACKING_2X8_PADHI,
-		.order			= SOC_MBUS_ORDER_LE,
-		.layout			= SOC_MBUS_LAYOUT_PLANAR_Y_C,
-	},
-};
-
-/* This will be corrected as we get more formats */
-static bool sh_mobile_ceu_packing_supported(const struct soc_mbus_pixelfmt *fmt)
-{
-	return	fmt->packing == SOC_MBUS_PACKING_NONE ||
-		(fmt->bits_per_sample == 8 &&
-		 fmt->packing == SOC_MBUS_PACKING_1_5X8) ||
-		(fmt->bits_per_sample == 8 &&
-		 fmt->packing == SOC_MBUS_PACKING_2X8_PADHI) ||
-		(fmt->bits_per_sample > 8 &&
-		 fmt->packing == SOC_MBUS_PACKING_EXTEND16);
-}
-
-static struct soc_camera_device *ctrl_to_icd(struct v4l2_ctrl *ctrl)
-{
-	return container_of(ctrl->handler, struct soc_camera_device,
-							ctrl_handler);
-}
-
-static int sh_mobile_ceu_s_ctrl(struct v4l2_ctrl *ctrl)
-{
-	struct soc_camera_device *icd = ctrl_to_icd(ctrl);
-	struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-
-	switch (ctrl->id) {
-	case V4L2_CID_SHARPNESS:
-		switch (icd->current_fmt->host_fmt->fourcc) {
-		case V4L2_PIX_FMT_NV12:
-		case V4L2_PIX_FMT_NV21:
-		case V4L2_PIX_FMT_NV16:
-		case V4L2_PIX_FMT_NV61:
-			ceu_write(pcdev, CLFCR, !ctrl->val);
-			return 0;
-		}
-		break;
-	}
-
-	return -EINVAL;
-}
-
-static const struct v4l2_ctrl_ops sh_mobile_ceu_ctrl_ops = {
-	.s_ctrl = sh_mobile_ceu_s_ctrl,
-};
-
-static int sh_mobile_ceu_get_formats(struct soc_camera_device *icd, unsigned int idx,
-				     struct soc_camera_format_xlate *xlate)
-{
-	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
-	struct device *dev = icd->parent;
-	struct soc_camera_host *ici = to_soc_camera_host(dev);
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-	int ret, k, n;
-	int formats = 0;
-	struct sh_mobile_ceu_cam *cam;
-	struct v4l2_subdev_mbus_code_enum code = {
-		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
-		.index = idx,
-	};
-	const struct soc_mbus_pixelfmt *fmt;
-
-	ret = v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code);
-	if (ret < 0)
-		/* No more formats */
-		return 0;
-
-	fmt = soc_mbus_get_fmtdesc(code.code);
-	if (!fmt) {
-		dev_warn(dev, "unsupported format code #%u: %d\n", idx, code.code);
-		return 0;
-	}
-
-	ret = sh_mobile_ceu_try_bus_param(icd, fmt->bits_per_sample);
-	if (ret < 0)
-		return 0;
-
-	if (!icd->host_priv) {
-		struct v4l2_subdev_format fmt = {
-			.which = V4L2_SUBDEV_FORMAT_ACTIVE,
-		};
-		struct v4l2_mbus_framefmt *mf = &fmt.format;
-		struct v4l2_rect rect;
-		int shift = 0;
-
-		/* Add our control */
-		v4l2_ctrl_new_std(&icd->ctrl_handler, &sh_mobile_ceu_ctrl_ops,
-				  V4L2_CID_SHARPNESS, 0, 1, 1, 1);
-		if (icd->ctrl_handler.error)
-			return icd->ctrl_handler.error;
-
-		/* FIXME: subwindow is lost between close / open */
-
-		/* Cache current client geometry */
-		ret = soc_camera_client_g_rect(sd, &rect);
-		if (ret < 0)
-			return ret;
-
-		/* First time */
-		ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt);
-		if (ret < 0)
-			return ret;
-
-		/*
-		 * All currently existing CEU implementations support 2560x1920
-		 * or larger frames. If the sensor is proposing too big a frame,
-		 * don't bother with possibly supportred by the CEU larger
-		 * sizes, just try VGA multiples. If needed, this can be
-		 * adjusted in the future.
-		 */
-		while ((mf->width > pcdev->max_width ||
-			mf->height > pcdev->max_height) && shift < 4) {
-			/* Try 2560x1920, 1280x960, 640x480, 320x240 */
-			mf->width	= 2560 >> shift;
-			mf->height	= 1920 >> shift;
-			ret = v4l2_device_call_until_err(sd->v4l2_dev,
-					soc_camera_grp_id(icd), pad,
-					set_fmt, NULL, &fmt);
-			if (ret < 0)
-				return ret;
-			shift++;
-		}
-
-		if (shift == 4) {
-			dev_err(dev, "Failed to configure the client below %ux%x\n",
-				mf->width, mf->height);
-			return -EIO;
-		}
-
-		dev_geo(dev, "camera fmt %ux%u\n", mf->width, mf->height);
-
-		cam = kzalloc(sizeof(*cam), GFP_KERNEL);
-		if (!cam)
-			return -ENOMEM;
-
-		/* We are called with current camera crop, initialise subrect with it */
-		cam->rect	= rect;
-		cam->subrect	= rect;
-
-		cam->width	= mf->width;
-		cam->height	= mf->height;
-
-		icd->host_priv = cam;
-	} else {
-		cam = icd->host_priv;
-	}
-
-	/* Beginning of a pass */
-	if (!idx)
-		cam->extra_fmt = NULL;
-
-	switch (code.code) {
-	case MEDIA_BUS_FMT_UYVY8_2X8:
-	case MEDIA_BUS_FMT_VYUY8_2X8:
-	case MEDIA_BUS_FMT_YUYV8_2X8:
-	case MEDIA_BUS_FMT_YVYU8_2X8:
-		if (cam->extra_fmt)
-			break;
-
-		/*
-		 * Our case is simple so far: for any of the above four camera
-		 * formats we add all our four synthesized NV* formats, so,
-		 * just marking the device with a single flag suffices. If
-		 * the format generation rules are more complex, you would have
-		 * to actually hang your already added / counted formats onto
-		 * the host_priv pointer and check whether the format you're
-		 * going to add now is already there.
-		 */
-		cam->extra_fmt = sh_mobile_ceu_formats;
-
-		n = ARRAY_SIZE(sh_mobile_ceu_formats);
-		formats += n;
-		for (k = 0; xlate && k < n; k++) {
-			xlate->host_fmt	= &sh_mobile_ceu_formats[k];
-			xlate->code	= code.code;
-			xlate++;
-			dev_dbg(dev, "Providing format %s using code %d\n",
-				sh_mobile_ceu_formats[k].name, code.code);
-		}
-		break;
-	default:
-		if (!sh_mobile_ceu_packing_supported(fmt))
-			return 0;
-	}
-
-	/* Generic pass-through */
-	formats++;
-	if (xlate) {
-		xlate->host_fmt	= fmt;
-		xlate->code	= code.code;
-		xlate++;
-		dev_dbg(dev, "Providing format %s in pass-through mode\n",
-			fmt->name);
-	}
-
-	return formats;
-}
-
-static void sh_mobile_ceu_put_formats(struct soc_camera_device *icd)
-{
-	kfree(icd->host_priv);
-	icd->host_priv = NULL;
-}
-
-#define scale_down(size, scale) soc_camera_shift_scale(size, 12, scale)
-#define calc_generic_scale(in, out) soc_camera_calc_scale(in, 12, out)
-
-/*
- * CEU can scale and crop, but we don't want to waste bandwidth and kill the
- * framerate by always requesting the maximum image from the client. See
- * Documentation/video4linux/sh_mobile_ceu_camera.txt for a description of
- * scaling and cropping algorithms and for the meaning of referenced here steps.
- */
-static int sh_mobile_ceu_set_selection(struct soc_camera_device *icd,
-				       struct v4l2_selection *sel)
-{
-	struct v4l2_rect *rect = &sel->r;
-	struct device *dev = icd->parent;
-	struct soc_camera_host *ici = to_soc_camera_host(dev);
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-	struct v4l2_selection cam_sel;
-	struct sh_mobile_ceu_cam *cam = icd->host_priv;
-	struct v4l2_rect *cam_rect = &cam_sel.r;
-	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
-	struct v4l2_subdev_format fmt = {
-		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
-	};
-	struct v4l2_mbus_framefmt *mf = &fmt.format;
-	unsigned int scale_cam_h, scale_cam_v, scale_ceu_h, scale_ceu_v,
-		out_width, out_height;
-	int interm_width, interm_height;
-	u32 capsr, cflcr;
-	int ret;
-
-	dev_geo(dev, "S_SELECTION(%ux%u@%u:%u)\n", rect->width, rect->height,
-		rect->left, rect->top);
-
-	/* During camera cropping its output window can change too, stop CEU */
-	capsr = capture_save_reset(pcdev);
-	dev_dbg(dev, "CAPSR 0x%x, CFLCR 0x%x\n", capsr, pcdev->cflcr);
-
-	/*
-	 * 1. - 2. Apply iterative camera S_SELECTION for new input window, read back
-	 * actual camera rectangle.
-	 */
-	ret = soc_camera_client_s_selection(sd, sel, &cam_sel,
-				       &cam->rect, &cam->subrect);
-	if (ret < 0)
-		return ret;
-
-	dev_geo(dev, "1-2: camera cropped to %ux%u@%u:%u\n",
-		cam_rect->width, cam_rect->height,
-		cam_rect->left, cam_rect->top);
-
-	/* On success cam_crop contains current camera crop */
-
-	/* 3. Retrieve camera output window */
-	ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt);
-	if (ret < 0)
-		return ret;
-
-	if (mf->width > pcdev->max_width || mf->height > pcdev->max_height)
-		return -EINVAL;
-
-	/* 4. Calculate camera scales */
-	scale_cam_h	= calc_generic_scale(cam_rect->width, mf->width);
-	scale_cam_v	= calc_generic_scale(cam_rect->height, mf->height);
-
-	/* Calculate intermediate window */
-	interm_width	= scale_down(rect->width, scale_cam_h);
-	interm_height	= scale_down(rect->height, scale_cam_v);
-
-	if (interm_width < icd->user_width) {
-		u32 new_scale_h;
-
-		new_scale_h = calc_generic_scale(rect->width, icd->user_width);
-
-		mf->width = scale_down(cam_rect->width, new_scale_h);
-	}
-
-	if (interm_height < icd->user_height) {
-		u32 new_scale_v;
-
-		new_scale_v = calc_generic_scale(rect->height, icd->user_height);
-
-		mf->height = scale_down(cam_rect->height, new_scale_v);
-	}
-
-	if (interm_width < icd->user_width || interm_height < icd->user_height) {
-		ret = v4l2_device_call_until_err(sd->v4l2_dev,
-					soc_camera_grp_id(icd), pad,
-					set_fmt, NULL, &fmt);
-		if (ret < 0)
-			return ret;
-
-		dev_geo(dev, "New camera output %ux%u\n", mf->width, mf->height);
-		scale_cam_h	= calc_generic_scale(cam_rect->width, mf->width);
-		scale_cam_v	= calc_generic_scale(cam_rect->height, mf->height);
-		interm_width	= scale_down(rect->width, scale_cam_h);
-		interm_height	= scale_down(rect->height, scale_cam_v);
-	}
-
-	/* Cache camera output window */
-	cam->width	= mf->width;
-	cam->height	= mf->height;
-
-	if (pcdev->image_mode) {
-		out_width	= min(interm_width, icd->user_width);
-		out_height	= min(interm_height, icd->user_height);
-	} else {
-		out_width	= interm_width;
-		out_height	= interm_height;
-	}
-
-	/*
-	 * 5. Calculate CEU scales from camera scales from results of (5) and
-	 *    the user window
-	 */
-	scale_ceu_h	= calc_scale(interm_width, &out_width);
-	scale_ceu_v	= calc_scale(interm_height, &out_height);
-
-	dev_geo(dev, "5: CEU scales %u:%u\n", scale_ceu_h, scale_ceu_v);
-
-	/* Apply CEU scales. */
-	cflcr = scale_ceu_h | (scale_ceu_v << 16);
-	if (cflcr != pcdev->cflcr) {
-		pcdev->cflcr = cflcr;
-		ceu_write(pcdev, CFLCR, cflcr);
-	}
-
-	icd->user_width	 = out_width & ~3;
-	icd->user_height = out_height & ~3;
-	/* Offsets are applied at the CEU scaling filter input */
-	cam->ceu_left	 = scale_down(rect->left - cam_rect->left, scale_cam_h) & ~1;
-	cam->ceu_top	 = scale_down(rect->top - cam_rect->top, scale_cam_v) & ~1;
-
-	/* 6. Use CEU cropping to crop to the new window. */
-	sh_mobile_ceu_set_rect(icd);
-
-	cam->subrect = *rect;
-
-	dev_geo(dev, "6: CEU cropped to %ux%u@%u:%u\n",
-		icd->user_width, icd->user_height,
-		cam->ceu_left, cam->ceu_top);
-
-	/* Restore capture. The CE bit can be cleared by the hardware */
-	if (pcdev->active)
-		capsr |= 1;
-	capture_restore(pcdev, capsr);
-
-	/* Even if only camera cropping succeeded */
-	return ret;
-}
-
-static int sh_mobile_ceu_get_selection(struct soc_camera_device *icd,
-				       struct v4l2_selection *sel)
-{
-	struct sh_mobile_ceu_cam *cam = icd->host_priv;
-
-	sel->r = cam->subrect;
-
-	return 0;
-}
-
-/* Similar to set_crop multistage iterative algorithm */
-static int sh_mobile_ceu_set_fmt(struct soc_camera_device *icd,
-				 struct v4l2_format *f)
-{
-	struct device *dev = icd->parent;
-	struct soc_camera_host *ici = to_soc_camera_host(dev);
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-	struct sh_mobile_ceu_cam *cam = icd->host_priv;
-	struct v4l2_pix_format *pix = &f->fmt.pix;
-	struct v4l2_mbus_framefmt mf;
-	__u32 pixfmt = pix->pixelformat;
-	const struct soc_camera_format_xlate *xlate;
-	unsigned int ceu_sub_width = pcdev->max_width,
-		ceu_sub_height = pcdev->max_height;
-	u16 scale_v, scale_h;
-	int ret;
-	bool image_mode;
-	enum v4l2_field field;
-
-	switch (pix->field) {
-	default:
-		pix->field = V4L2_FIELD_NONE;
-		/* fall-through */
-	case V4L2_FIELD_INTERLACED_TB:
-	case V4L2_FIELD_INTERLACED_BT:
-	case V4L2_FIELD_NONE:
-		field = pix->field;
-		break;
-	case V4L2_FIELD_INTERLACED:
-		field = V4L2_FIELD_INTERLACED_TB;
-		break;
-	}
-
-	xlate = soc_camera_xlate_by_fourcc(icd, pixfmt);
-	if (!xlate) {
-		dev_warn(dev, "Format %x not found\n", pixfmt);
-		return -EINVAL;
-	}
-
-	/* 1.-4. Calculate desired client output geometry */
-	soc_camera_calc_client_output(icd, &cam->rect, &cam->subrect, pix, &mf, 12);
-	mf.field	= pix->field;
-	mf.colorspace	= pix->colorspace;
-	mf.code		= xlate->code;
-
-	switch (pixfmt) {
-	case V4L2_PIX_FMT_NV12:
-	case V4L2_PIX_FMT_NV21:
-	case V4L2_PIX_FMT_NV16:
-	case V4L2_PIX_FMT_NV61:
-		image_mode = true;
-		break;
-	default:
-		image_mode = false;
-	}
-
-	dev_geo(dev, "S_FMT(pix=0x%x, fld 0x%x, code 0x%x, %ux%u)\n", pixfmt, mf.field, mf.code,
-		pix->width, pix->height);
-
-	dev_geo(dev, "4: request camera output %ux%u\n", mf.width, mf.height);
-
-	/* 5. - 9. */
-	ret = soc_camera_client_scale(icd, &cam->rect, &cam->subrect,
-				&mf, &ceu_sub_width, &ceu_sub_height,
-				image_mode && V4L2_FIELD_NONE == field, 12);
-
-	dev_geo(dev, "5-9: client scale return %d\n", ret);
-
-	/* Done with the camera. Now see if we can improve the result */
-
-	dev_geo(dev, "fmt %ux%u, requested %ux%u\n",
-		mf.width, mf.height, pix->width, pix->height);
-	if (ret < 0)
-		return ret;
-
-	if (mf.code != xlate->code)
-		return -EINVAL;
-
-	/* 9. Prepare CEU crop */
-	cam->width = mf.width;
-	cam->height = mf.height;
-
-	/* 10. Use CEU scaling to scale to the requested user window. */
-
-	/* We cannot scale up */
-	if (pix->width > ceu_sub_width)
-		ceu_sub_width = pix->width;
-
-	if (pix->height > ceu_sub_height)
-		ceu_sub_height = pix->height;
-
-	pix->colorspace = mf.colorspace;
-
-	if (image_mode) {
-		/* Scale pix->{width x height} down to width x height */
-		scale_h		= calc_scale(ceu_sub_width, &pix->width);
-		scale_v		= calc_scale(ceu_sub_height, &pix->height);
-	} else {
-		pix->width	= ceu_sub_width;
-		pix->height	= ceu_sub_height;
-		scale_h		= 0;
-		scale_v		= 0;
-	}
-
-	pcdev->cflcr = scale_h | (scale_v << 16);
-
-	/*
-	 * We have calculated CFLCR, the actual configuration will be performed
-	 * in sh_mobile_ceu_set_bus_param()
-	 */
-
-	dev_geo(dev, "10: W: %u : 0x%x = %u, H: %u : 0x%x = %u\n",
-		ceu_sub_width, scale_h, pix->width,
-		ceu_sub_height, scale_v, pix->height);
-
-	cam->code		= xlate->code;
-	icd->current_fmt	= xlate;
-
-	pcdev->field = field;
-	pcdev->image_mode = image_mode;
-
-	/* CFSZR requirement */
-	pix->width	&= ~3;
-	pix->height	&= ~3;
-
-	return 0;
-}
-
-#define CEU_CHDW_MAX	8188U	/* Maximum line stride */
-
-static int sh_mobile_ceu_try_fmt(struct soc_camera_device *icd,
-				 struct v4l2_format *f)
-{
-	struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-	const struct soc_camera_format_xlate *xlate;
-	struct v4l2_pix_format *pix = &f->fmt.pix;
-	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
-	struct v4l2_subdev_pad_config pad_cfg;
-	struct v4l2_subdev_format format = {
-		.which = V4L2_SUBDEV_FORMAT_TRY,
-	};
-	struct v4l2_mbus_framefmt *mf = &format.format;
-	__u32 pixfmt = pix->pixelformat;
-	int width, height;
-	int ret;
-
-	dev_geo(icd->parent, "TRY_FMT(pix=0x%x, %ux%u)\n",
-		 pixfmt, pix->width, pix->height);
-
-	xlate = soc_camera_xlate_by_fourcc(icd, pixfmt);
-	if (!xlate) {
-		xlate = icd->current_fmt;
-		dev_dbg(icd->parent, "Format %x not found, keeping %x\n",
-			pixfmt, xlate->host_fmt->fourcc);
-		pixfmt = xlate->host_fmt->fourcc;
-		pix->pixelformat = pixfmt;
-		pix->colorspace = icd->colorspace;
-	}
-
-	/* FIXME: calculate using depth and bus width */
-
-	/* CFSZR requires height and width to be 4-pixel aligned */
-	v4l_bound_align_image(&pix->width, 2, pcdev->max_width, 2,
-			      &pix->height, 4, pcdev->max_height, 2, 0);
-
-	width = pix->width;
-	height = pix->height;
-
-	/* limit to sensor capabilities */
-	mf->width	= pix->width;
-	mf->height	= pix->height;
-	mf->field	= pix->field;
-	mf->code	= xlate->code;
-	mf->colorspace	= pix->colorspace;
-
-	ret = v4l2_device_call_until_err(sd->v4l2_dev, soc_camera_grp_id(icd),
-					 pad, set_fmt, &pad_cfg, &format);
-	if (ret < 0)
-		return ret;
-
-	pix->width	= mf->width;
-	pix->height	= mf->height;
-	pix->field	= mf->field;
-	pix->colorspace	= mf->colorspace;
-
-	switch (pixfmt) {
-	case V4L2_PIX_FMT_NV12:
-	case V4L2_PIX_FMT_NV21:
-	case V4L2_PIX_FMT_NV16:
-	case V4L2_PIX_FMT_NV61:
-		/* FIXME: check against rect_max after converting soc-camera */
-		/* We can scale precisely, need a bigger image from camera */
-		if (pix->width < width || pix->height < height) {
-			/*
-			 * We presume, the sensor behaves sanely, i.e., if
-			 * requested a bigger rectangle, it will not return a
-			 * smaller one.
-			 */
-			mf->width = pcdev->max_width;
-			mf->height = pcdev->max_height;
-			ret = v4l2_device_call_until_err(sd->v4l2_dev,
-					soc_camera_grp_id(icd), pad,
-					set_fmt, &pad_cfg, &format);
-			if (ret < 0) {
-				/* Shouldn't actually happen... */
-				dev_err(icd->parent,
-					"FIXME: client try_fmt() = %d\n", ret);
-				return ret;
-			}
-		}
-		/* We will scale exactly */
-		if (mf->width > width)
-			pix->width = width;
-		if (mf->height > height)
-			pix->height = height;
-
-		pix->bytesperline = max(pix->bytesperline, pix->width);
-		pix->bytesperline = min(pix->bytesperline, CEU_CHDW_MAX);
-		pix->bytesperline &= ~3;
-		break;
-
-	default:
-		/* Configurable stride isn't supported in pass-through mode. */
-		pix->bytesperline  = 0;
-	}
-
-	pix->width	&= ~3;
-	pix->height	&= ~3;
-	pix->sizeimage	= 0;
-
-	dev_geo(icd->parent, "%s(): return %d, fmt 0x%x, %ux%u\n",
-		__func__, ret, pix->pixelformat, pix->width, pix->height);
-
-	return ret;
-}
-
-static int sh_mobile_ceu_set_liveselection(struct soc_camera_device *icd,
-					   struct v4l2_selection *sel)
-{
-	struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
-	struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
-	struct sh_mobile_ceu_dev *pcdev = ici->priv;
-	u32 out_width = icd->user_width, out_height = icd->user_height;
-	int ret;
-
-	/* Freeze queue */
-	pcdev->frozen = 1;
-	/* Wait for frame */
-	ret = wait_for_completion_interruptible(&pcdev->complete);
-	/* Stop the client */
-	ret = v4l2_subdev_call(sd, video, s_stream, 0);
-	if (ret < 0)
-		dev_warn(icd->parent,
-			 "Client failed to stop the stream: %d\n", ret);
-	else
-		/* Do the crop, if it fails, there's nothing more we can do */
-		sh_mobile_ceu_set_selection(icd, sel);
-
-	dev_geo(icd->parent, "Output after crop: %ux%u\n", icd->user_width, icd->user_height);
-
-	if (icd->user_width != out_width || icd->user_height != out_height) {
-		struct v4l2_format f = {
-			.type	= V4L2_BUF_TYPE_VIDEO_CAPTURE,
-			.fmt.pix	= {
-				.width		= out_width,
-				.height		= out_height,
-				.pixelformat	= icd->current_fmt->host_fmt->fourcc,
-				.field		= pcdev->field,
-				.colorspace	= icd->colorspace,
-			},
-		};
-		ret = sh_mobile_ceu_set_fmt(icd, &f);
-		if (!ret && (out_width != f.fmt.pix.width ||
-			     out_height != f.fmt.pix.height))
-			ret = -EINVAL;
-		if (!ret) {
-			icd->user_width		= out_width & ~3;
-			icd->user_height	= out_height & ~3;
-			ret = sh_mobile_ceu_set_bus_param(icd);
-		}
-	}
-
-	/* Thaw the queue */
-	pcdev->frozen = 0;
-	spin_lock_irq(&pcdev->lock);
-	sh_mobile_ceu_capture(pcdev);
-	spin_unlock_irq(&pcdev->lock);
-	/* Start the client */
-	ret = v4l2_subdev_call(sd, video, s_stream, 1);
-	return ret;
-}
-
-static unsigned int sh_mobile_ceu_poll(struct file *file, poll_table *pt)
-{
-	struct soc_camera_device *icd = file->private_data;
-
-	return vb2_poll(&icd->vb2_vidq, file, pt);
-}
-
-static int sh_mobile_ceu_querycap(struct soc_camera_host *ici,
-				  struct v4l2_capability *cap)
-{
-	strlcpy(cap->card, "SuperH_Mobile_CEU", sizeof(cap->card));
-	strlcpy(cap->driver, "sh_mobile_ceu", sizeof(cap->driver));
-	strlcpy(cap->bus_info, "platform:sh_mobile_ceu", sizeof(cap->bus_info));
-	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
-	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
-
-	return 0;
-}
-
-static int sh_mobile_ceu_init_videobuf(struct vb2_queue *q,
-				       struct soc_camera_device *icd)
-{
-	struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
-
-	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-	q->io_modes = VB2_MMAP | VB2_USERPTR;
-	q->drv_priv = icd;
-	q->ops = &sh_mobile_ceu_videobuf_ops;
-	q->mem_ops = &vb2_dma_contig_memops;
-	q->buf_struct_size = sizeof(struct sh_mobile_ceu_buffer);
-	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
-	q->lock = &ici->host_lock;
-	q->dev = ici->v4l2_dev.dev;
-
-	return vb2_queue_init(q);
-}
-
-static struct soc_camera_host_ops sh_mobile_ceu_host_ops = {
-	.owner		= THIS_MODULE,
-	.add		= sh_mobile_ceu_add_device,
-	.remove		= sh_mobile_ceu_remove_device,
-	.clock_start	= sh_mobile_ceu_clock_start,
-	.clock_stop	= sh_mobile_ceu_clock_stop,
-	.get_formats	= sh_mobile_ceu_get_formats,
-	.put_formats	= sh_mobile_ceu_put_formats,
-	.get_selection	= sh_mobile_ceu_get_selection,
-	.set_selection	= sh_mobile_ceu_set_selection,
-	.set_liveselection	= sh_mobile_ceu_set_liveselection,
-	.set_fmt	= sh_mobile_ceu_set_fmt,
-	.try_fmt	= sh_mobile_ceu_try_fmt,
-	.poll		= sh_mobile_ceu_poll,
-	.querycap	= sh_mobile_ceu_querycap,
-	.set_bus_param	= sh_mobile_ceu_set_bus_param,
-	.init_videobuf2	= sh_mobile_ceu_init_videobuf,
-};
-
-struct bus_wait {
-	struct notifier_block	notifier;
-	struct completion	completion;
-	struct device		*dev;
-};
-
-static int bus_notify(struct notifier_block *nb,
-		      unsigned long action, void *data)
-{
-	struct device *dev = data;
-	struct bus_wait *wait = container_of(nb, struct bus_wait, notifier);
-
-	if (wait->dev != dev)
-		return NOTIFY_DONE;
-
-	switch (action) {
-	case BUS_NOTIFY_UNBOUND_DRIVER:
-		/* Protect from module unloading */
-		wait_for_completion(&wait->completion);
-		return NOTIFY_OK;
-	}
-	return NOTIFY_DONE;
-}
-
-static int sh_mobile_ceu_probe(struct platform_device *pdev)
-{
-	struct sh_mobile_ceu_dev *pcdev;
-	struct resource *res;
-	void __iomem *base;
-	unsigned int irq;
-	int err;
-	struct bus_wait wait = {
-		.completion = COMPLETION_INITIALIZER_ONSTACK(wait.completion),
-		.notifier.notifier_call = bus_notify,
-	};
-
-	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-	irq = platform_get_irq(pdev, 0);
-	if (!res || (int)irq <= 0) {
-		dev_err(&pdev->dev, "Not enough CEU platform resources.\n");
-		return -ENODEV;
-	}
-
-	pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL);
-	if (!pcdev) {
-		dev_err(&pdev->dev, "Could not allocate pcdev\n");
-		return -ENOMEM;
-	}
-
-	INIT_LIST_HEAD(&pcdev->capture);
-	spin_lock_init(&pcdev->lock);
-	init_completion(&pcdev->complete);
-
-	pcdev->pdata = pdev->dev.platform_data;
-	if (!pcdev->pdata && !pdev->dev.of_node) {
-		dev_err(&pdev->dev, "CEU platform data not set.\n");
-		return -EINVAL;
-	}
-
-	/* TODO: implement per-device bus flags */
-	if (pcdev->pdata) {
-		pcdev->max_width = pcdev->pdata->max_width;
-		pcdev->max_height = pcdev->pdata->max_height;
-		pcdev->flags = pcdev->pdata->flags;
-	}
-	pcdev->field = V4L2_FIELD_NONE;
-
-	if (!pcdev->max_width) {
-		unsigned int v;
-		err = of_property_read_u32(pdev->dev.of_node, "renesas,max-width", &v);
-		if (!err)
-			pcdev->max_width = v;
-
-		if (!pcdev->max_width)
-			pcdev->max_width = 2560;
-	}
-	if (!pcdev->max_height) {
-		unsigned int v;
-		err = of_property_read_u32(pdev->dev.of_node, "renesas,max-height", &v);
-		if (!err)
-			pcdev->max_height = v;
-
-		if (!pcdev->max_height)
-			pcdev->max_height = 1920;
-	}
-
-	base = devm_ioremap_resource(&pdev->dev, res);
-	if (IS_ERR(base))
-		return PTR_ERR(base);
-
-	pcdev->irq = irq;
-	pcdev->base = base;
-	pcdev->video_limit = 0; /* only enabled if second resource exists */
-
-	res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
-	if (res) {
-		err = dma_declare_coherent_memory(&pdev->dev, res->start,
-						  res->start,
-						  resource_size(res),
-						  DMA_MEMORY_MAP |
-						  DMA_MEMORY_EXCLUSIVE);
-		if (!err) {
-			dev_err(&pdev->dev, "Unable to declare CEU memory.\n");
-			return -ENXIO;
-		}
-
-		pcdev->video_limit = resource_size(res);
-	}
-
-	/* request irq */
-	err = devm_request_irq(&pdev->dev, pcdev->irq, sh_mobile_ceu_irq,
-			       0, dev_name(&pdev->dev), pcdev);
-	if (err) {
-		dev_err(&pdev->dev, "Unable to register CEU interrupt.\n");
-		goto exit_release_mem;
-	}
-
-	pm_suspend_ignore_children(&pdev->dev, true);
-	pm_runtime_enable(&pdev->dev);
-	pm_runtime_resume(&pdev->dev);
-
-	pcdev->ici.priv = pcdev;
-	pcdev->ici.v4l2_dev.dev = &pdev->dev;
-	pcdev->ici.nr = pdev->id;
-	pcdev->ici.drv_name = dev_name(&pdev->dev);
-	pcdev->ici.ops = &sh_mobile_ceu_host_ops;
-	pcdev->ici.capabilities = SOCAM_HOST_CAP_STRIDE;
-
-	if (pcdev->pdata && pcdev->pdata->asd_sizes) {
-		pcdev->ici.asd = pcdev->pdata->asd;
-		pcdev->ici.asd_sizes = pcdev->pdata->asd_sizes;
-	}
-
-	err = soc_camera_host_register(&pcdev->ici);
-	if (err)
-		goto exit_free_clk;
-
-	return 0;
-
-exit_free_clk:
-	pm_runtime_disable(&pdev->dev);
-exit_release_mem:
-	if (platform_get_resource(pdev, IORESOURCE_MEM, 1))
-		dma_release_declared_memory(&pdev->dev);
-	return err;
-}
-
-static int sh_mobile_ceu_remove(struct platform_device *pdev)
-{
-	struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev);
-
-	soc_camera_host_unregister(soc_host);
-	pm_runtime_disable(&pdev->dev);
-	if (platform_get_resource(pdev, IORESOURCE_MEM, 1))
-		dma_release_declared_memory(&pdev->dev);
-
-	return 0;
-}
-
-static int sh_mobile_ceu_runtime_nop(struct device *dev)
-{
-	/* Runtime PM callback shared between ->runtime_suspend()
-	 * and ->runtime_resume(). Simply returns success.
-	 *
-	 * This driver re-initializes all registers after
-	 * pm_runtime_get_sync() anyway so there is no need
-	 * to save and restore registers here.
-	 */
-	return 0;
-}
-
-static const struct dev_pm_ops sh_mobile_ceu_dev_pm_ops = {
-	.runtime_suspend = sh_mobile_ceu_runtime_nop,
-	.runtime_resume = sh_mobile_ceu_runtime_nop,
-};
-
-static const struct of_device_id sh_mobile_ceu_of_match[] = {
-	{ .compatible = "renesas,sh-mobile-ceu" },
-	{ }
-};
-MODULE_DEVICE_TABLE(of, sh_mobile_ceu_of_match);
-
-static struct platform_driver sh_mobile_ceu_driver = {
-	.driver		= {
-		.name	= "sh_mobile_ceu",
-		.pm	= &sh_mobile_ceu_dev_pm_ops,
-		.of_match_table = sh_mobile_ceu_of_match,
-	},
-	.probe		= sh_mobile_ceu_probe,
-	.remove		= sh_mobile_ceu_remove,
-};
-
-static int __init sh_mobile_ceu_init(void)
-{
-	return platform_driver_register(&sh_mobile_ceu_driver);
-}
-
-static void __exit sh_mobile_ceu_exit(void)
-{
-	platform_driver_unregister(&sh_mobile_ceu_driver);
-}
-
-module_init(sh_mobile_ceu_init);
-module_exit(sh_mobile_ceu_exit);
-
-MODULE_DESCRIPTION("SuperH Mobile CEU driver");
-MODULE_AUTHOR("Magnus Damm");
-MODULE_LICENSE("GPL");
-MODULE_VERSION("0.1.0");
-MODULE_ALIAS("platform:sh_mobile_ceu");
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC v4 3/8] media: platform: Add Renesas CEU driver
  2017-05-19 16:02 [RFC v4 0/8] Renesas CEU driver Jacopo Mondi
  2017-05-19 16:02 ` [RFC v4 1/8] include: media: Move and update CEU driver interface Jacopo Mondi
  2017-05-19 16:02 ` [RFC v4 2/8] media: platform: soc-camera: Remove SH CEU driver Jacopo Mondi
@ 2017-05-19 16:02 ` Jacopo Mondi
  2017-05-19 16:02 ` [RFC v4 4/8] media: platform: ceu: Support for multiple subdevs Jacopo Mondi
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Jacopo Mondi @ 2017-05-19 16:02 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm; +Cc: linux-renesas-soc

Add driver for Renesas Capture Engine Unit (CEU).

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
---
 drivers/media/platform/Kconfig       |    8 +
 drivers/media/platform/Makefile      |    2 +
 drivers/media/platform/renesas-ceu.c | 1525 ++++++++++++++++++++++++++++++++++
 3 files changed, 1535 insertions(+)
 create mode 100644 drivers/media/platform/renesas-ceu.c

diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index c9106e1..226a4fd 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -114,6 +114,14 @@ config VIDEO_S3C_CAMIF
 	  To compile this driver as a module, choose M here: the module
 	  will be called s3c-camif.

+config VIDEO_RENESAS_CEU
+	tristate "Renesas Capture Engine Unit (CEU) driver"
+	depends on VIDEO_DEV && VIDEO_V4L2 && HAS_DMA
+	depends on ARCH_SHMOBILE || ARCH_R7S72100 || COMPILE_TEST
+	select VIDEOBUF2_DMA_CONTIG
+	---help---
+	  This is a v4l2 driver for the Renesas CEU Interface
+
 source "drivers/media/platform/soc_camera/Kconfig"
 source "drivers/media/platform/exynos4-is/Kconfig"
 source "drivers/media/platform/am437x/Kconfig"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 349ddf6..72f92f5 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -25,6 +25,8 @@ obj-$(CONFIG_VIDEO_CODA) 		+= coda/

 obj-$(CONFIG_VIDEO_SH_VEU)		+= sh_veu.o

+obj-$(CONFIG_VIDEO_RENESAS_CEU)		+= renesas-ceu.o
+
 obj-$(CONFIG_VIDEO_MEM2MEM_DEINTERLACE)	+= m2m-deinterlace.o

 obj-$(CONFIG_VIDEO_S3C_CAMIF) 		+= s3c-camif/
diff --git a/drivers/media/platform/renesas-ceu.c b/drivers/media/platform/renesas-ceu.c
new file mode 100644
index 0000000..8ebc2547
--- /dev/null
+++ b/drivers/media/platform/renesas-ceu.c
@@ -0,0 +1,1525 @@
+/*
+ * V4L2 Driver for Renesas CEU interface
+ *
+ * Copyright (C) 2017 Jacopo Mondi <jacopo+renesas@jmondi.org>
+ *
+ * Based on soc-camera driver "soc_camera/sh_mobile_ceu_camera.c"
+ *
+ * Copyright (C) 2008 Magnus Damm
+ *
+ * Based on V4L2 Driver for PXA camera host - "pxa_camera.c",
+ *
+ * Copyright (C) 2006, Sascha Hauer, Pengutronix
+ * Copyright (C) 2008, Guennadi Liakhovetski <kernel@pengutronix.de>
+ *
+ * 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/completion.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-async.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-image-sizes.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/v4l2-mediabus.h>
+
+#include <media/drv-intf/renesas_ceu.h>
+
+#define DRIVER_NAME	"renesas-ceu"
+
+/* ----------------------------------------------------------------------------
+ * CEU registers offsets and masks
+ */
+#define CEU_CAPSR	0x00 /* Capture start register */
+#define CEU_CAPCR	0x04 /* Capture control register */
+#define CEU_CAMCR	0x08 /* Capture interface control register */
+#define CEU_CMCYR	0x0c /* Capture interface cycle  register */
+#define CEU_CAMOR	0x10 /* Capture interface offset register */
+#define CEU_CAPWR	0x14 /* Capture interface width register */
+#define CEU_CAIFR	0x18 /* Capture interface input format register */
+#define CEU_CSTCR	0x20 /* Camera strobe control register (<= sh7722) */
+#define CEU_CSECR	0x24 /* Camera strobe emission count register (<= sh7722) */
+#define CEU_CRCNTR	0x28 /* CEU register control register */
+#define CEU_CRCMPR	0x2c /* CEU register forcible control register */
+#define CEU_CFLCR	0x30 /* Capture filter control register */
+#define CEU_CFSZR	0x34 /* Capture filter size clip register */
+#define CEU_CDWDR	0x38 /* Capture destination width register */
+#define CEU_CDAYR	0x3c /* Capture data address Y register */
+#define CEU_CDACR	0x40 /* Capture data address C register */
+#define CEU_CDBYR	0x44 /* Capture data bottom-field address Y register */
+#define CEU_CDBCR	0x48 /* Capture data bottom-field address C register */
+#define CEU_CBDSR	0x4c /* Capture bundle destination size register */
+#define CEU_CFWCR	0x5c /* Firewall operation control register */
+#define CEU_CLFCR	0x60 /* Capture low-pass filter control register */
+#define CEU_CDOCR	0x64 /* Capture data output control register */
+#define CEU_CDDCR	0x68 /* Capture data complexity level register */
+#define CEU_CDDAR	0x6c /* Capture data complexity level address register */
+#define CEU_CEIER	0x70 /* Capture event interrupt enable register */
+#define CEU_CETCR	0x74 /* Capture event flag clear register */
+#define CEU_CSTSR	0x7c /* Capture status register */
+#define CEU_CSRTR	0x80 /* Capture software reset register */
+#define CEU_CDSSR	0x84 /* Capture data size register */
+#define CEU_CDAYR2	0x90 /* Capture data address Y register 2 */
+#define CEU_CDACR2	0x94 /* Capture data address C register 2 */
+#define CEU_CDBYR2	0x98 /* Capture data bottom-field address Y register 2 */
+#define CEU_CDBCR2	0x9c /* Capture data bottom-field address C register 2 */
+
+/* Input components ordering: CEU_CAMCR.DTARY field */
+#define CEU_CAMCR_DTARY_BITSHIFT	8
+#define CEU_CAMCR_DTARY_8_UYVY		(0x00 << CEU_CAMCR_DTARY_BITSHIFT)
+#define CEU_CAMCR_DTARY_8_VYUY		(0x01 << CEU_CAMCR_DTARY_BITSHIFT)
+#define CEU_CAMCR_DTARY_8_YUYV		(0x02 << CEU_CAMCR_DTARY_BITSHIFT)
+#define CEU_CAMCR_DTARY_8_YVYU		(0x03 << CEU_CAMCR_DTARY_BITSHIFT)
+/* TODO: input components ordering for 16 bits input */
+
+/* Bus transfer MTU */
+#define CEU_CAPCR_MTCM_BITSHIFT		20
+#define CEU_CAPCR_BUS_WIDTH256		(0x3 << CEU_CAPCR_MTCM_BITSHIFT)
+
+/* Bus width configuration */
+#define CEU_CAMCR_DTIF_BITSHIFT		12
+#define CEU_CAMCR_DTIF_8BITS		(0 << CEU_CAMCR_DTIF_BITSHIFT)
+#define CEU_CAMCR_DTIF_16BITS		BIT(CEU_CAMCR_DTIF_BITSHIFT)
+
+/* No downsampling to planar YUV420 in image fetch mode */
+#define CEU_CDOCR_NO_DOWSAMPLE		BIT(4)
+
+/* Capture reset and enable bits */
+#define CEU_CAPSR_CPKIL			BIT(16)
+#define CEU_CAPSR_CE			BIT(0)
+
+/* CEU operating flag bit */
+#define CEU_CSTRST_CPTON		BIT(1)
+
+/* Acknowledge magical interrupt sources */
+#define CEU_CETCR_MAGIC			0x0317f313
+/* Prohibited register access interrupt bit */
+#define CEU_CETCR_IGRW			BIT(4)
+/* One-frame capture end interrupt */
+#define CEU_CEIER_CPE			BIT(0)
+/* VBP error */
+#define CEU_CEIER_VBP			BIT(20)
+/* Continuous capture mode (if set) */
+#define CEU_CAPCR_CTNCP			BIT(16)
+#define CEU_CEIER_MASK			(CEU_CEIER_CPE | CEU_CEIER_VBP)
+
+#define CEU_BUS_FLAGS (V4L2_MBUS_MASTER		     |  \
+			V4L2_MBUS_PCLK_SAMPLE_RISING |	\
+			V4L2_MBUS_HSYNC_ACTIVE_HIGH  |	\
+			V4L2_MBUS_HSYNC_ACTIVE_LOW   |	\
+			V4L2_MBUS_VSYNC_ACTIVE_HIGH  |	\
+			V4L2_MBUS_VSYNC_ACTIVE_LOW   |	\
+			V4L2_MBUS_DATA_ACTIVE_HIGH)
+
+#define CEU_MAX_WIDTH	2560
+#define CEU_MAX_HEIGHT	1920
+#define CEU_W_MAX(w)	((w) < CEU_MAX_WIDTH ? (w) : CEU_MAX_WIDTH)
+#define CEU_H_MAX(h)	((h) < CEU_MAX_HEIGHT ? (h) : CEU_MAX_HEIGHT)
+
+/* ----------------------------------------------------------------------------
+ * CEU formats
+ */
+
+/**
+ * ceu_bus_fmt - describe a 8-bits yuyv format the sensor can produce
+ *
+ * @mbus_code: bus format code
+ * @fmt_order: CEU_CAMCR.DTARY ordering of input components (Y, Cb, Cr)
+ * @fmt_order_swap: swapped CEU_CAMCR.DTARY ordering of input components
+ *		    (Y, Cr, Cb)
+ * @swapped: does Cr appear before Cb?
+ * @bps: number of bits sent over bus for each sample
+ */
+struct ceu_mbus_fmt {
+	u32	mbus_code;
+	u32	fmt_order;
+	u32	fmt_order_swap;
+	bool	swapped;
+	u8	bps;
+};
+
+/**
+ * ceu_buffer - Link vb2 buffer to the list of active buffers
+ */
+struct ceu_buffer {
+	struct vb2_v4l2_buffer vb;
+	struct list_head queue;
+};
+
+static inline struct ceu_buffer *vb2_to_ceu(struct vb2_v4l2_buffer *vbuf)
+{
+	return container_of(vbuf, struct ceu_buffer, vb);
+}
+
+/**
+ * ceu_device - CEU device instance
+ */
+struct ceu_device {
+	struct device		*dev;
+	struct video_device	vdev;
+	struct v4l2_device	v4l2_dev;
+
+	struct v4l2_subdev	*sensor;
+	struct ceu_mbus_fmt	sensor_fmt;
+
+	struct v4l2_async_notifier	notifier;
+	struct v4l2_async_subdev	asd;
+	struct v4l2_async_subdev	*asds[1];
+
+	/* vb2 queue, capture buffer list and active buffer pointer */
+	struct vb2_queue	vb2_vq;
+	struct list_head	capture;
+	struct vb2_v4l2_buffer	*active;
+	unsigned int		sequence;
+
+	/* mlock - locks on open/close and vb2 operations */
+	struct mutex	mlock;
+
+	/* lock - lock access to capture buffer queue and active buffer */
+	spinlock_t	lock;
+
+	void __iomem	*base;
+
+	enum v4l2_field		field;
+	struct v4l2_pix_format	v4l2_pix;
+
+	unsigned long		mbus_flags;
+	struct ceu_info		*pdata;
+};
+
+static inline struct ceu_device *v4l2_to_ceu(struct v4l2_device *v4l2_dev)
+{
+	return container_of(v4l2_dev, struct ceu_device, v4l2_dev);
+}
+
+/* ----------------------------------------------------------------------------
+ * CEU memory output formats
+ */
+
+/**
+ * ceu_fmt - describe a memory output format supported by CEU interface
+ *
+ * @fourcc: memory layout fourcc format code
+ * @bpp: bit for each pixel stored in memory
+ */
+struct ceu_fmt {
+	u32	fourcc;
+	u8	bpp;
+};
+
+/**
+ * ceu_format_list - List of supported memory output formats
+ *
+ * If sensor provides any YUYV bus format, all the following planar memory
+ * formats are available thanks to CEU re-ordering and sub-sampling.
+ *
+ * TODO: extend with packed YUYV (data synch fetch mode)
+ * TODO: extend with binary and RAW (data synch fetch mode)
+ */
+static const struct ceu_fmt ceu_fmt_list[] = {
+	{
+		.fourcc	= V4L2_PIX_FMT_NV16,
+		.bpp	= 16,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_NV61,
+		.bpp	= 16,
+	},
+	{
+		.fourcc	= V4L2_PIX_FMT_NV12,
+		.bpp	= 12,
+	},
+	{
+		.fourcc	= V4L2_PIX_FMT_NV21,
+		.bpp	= 12,
+	},
+};
+
+static const struct ceu_fmt *get_ceu_fmt_from_fourcc(unsigned int fourcc)
+{
+	const struct ceu_fmt *fmt = &ceu_fmt_list[0];
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(ceu_fmt_list); i++, fmt++)
+		if (fmt->fourcc == fourcc)
+			return fmt;
+
+	return NULL;
+}
+
+/* ----------------------------------------------------------------------------
+ * CEU HW operations
+ */
+static void ceu_write(struct ceu_device *priv, unsigned int reg_offs, u32 data)
+{
+	iowrite32(data, priv->base + reg_offs);
+}
+
+static u32 ceu_read(struct ceu_device *priv, unsigned int reg_offs)
+{
+	return ioread32(priv->base + reg_offs);
+}
+
+/**
+ * ceu_soft_reset() - Software reset the CEU interface
+ *
+ * TODO: characterize delays (Figure 46.6)
+ */
+static int ceu_soft_reset(struct ceu_device *ceudev)
+{
+	unsigned int reset_done;
+	unsigned int i;
+
+	ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CPKIL);
+	/*
+	 * FIXME: the manual says "clear the CE bit when set CPKIL", well,
+	 * I'm doing it even if the original driver did not
+	 */
+	ceu_write(ceudev, CEU_CAPSR, ~CEU_CAPSR_CE);
+
+	reset_done = 0;
+	for (i = 0; i < 1000 && !reset_done; i++) {
+		udelay(1);
+		if (!(ceu_read(ceudev, CEU_CSTSR) & CEU_CSTRST_CPTON))
+			reset_done++;
+
+	}
+
+	if (!reset_done) {
+		dev_warn(&ceudev->vdev.dev, "soft reset time out\n");
+		return -EIO;
+	}
+
+	reset_done = 0;
+	for (i = 0; i < 1000; i++) {
+		udelay(1);
+		if (!(ceu_read(ceudev, CEU_CAPSR) & CEU_CAPSR_CPKIL))
+			return 0;
+	}
+
+	/* if we get here, CEU has not reset properly */
+	return -EIO;
+}
+
+/* ----------------------------------------------------------------------------
+ * CEU Capture Operations
+ */
+
+/**
+ * ceu_capture() - Trigger start of a capture sequence
+ *
+ * Return value doesn't reflect the success/failure to queue the new buffer,
+ * but rather the status of the previous buffer.
+ */
+static int ceu_capture(struct ceu_device *ceudev)
+{
+	struct v4l2_pix_format *pix = &ceudev->v4l2_pix;
+	dma_addr_t phys_addr_top, phys_addr_bottom;
+	unsigned long bottom1, bottom2;
+	unsigned long top1, top2;
+	u32 status;
+	int ret = 0;
+
+	/*
+	 * The hardware is _very_ picky about this sequence. Especially
+	 * the CEU_CETCR_MAGIC value. It seems like we need to acknowledge
+	 * several not-so-well documented interrupt sources in CETCR.
+	 */
+	ceu_write(ceudev, CEU_CEIER,
+		  ceu_read(ceudev, CEU_CEIER) & ~CEU_CEIER_MASK);
+	status = ceu_read(ceudev, CEU_CETCR);
+	ceu_write(ceudev, CEU_CETCR, ~status & CEU_CETCR_MAGIC);
+
+	/*
+	 * FIXME: return error to user space reset interface in stop_streaming
+	 * When a VBP interrupt occurs, a capture end interrupt does not occur
+	 * and the image of that frame is not captured correctly.
+	 */
+	if (status & CEU_CEIER_VBP)
+		return -EIO;
+
+	/* Re-enable interrupt and set one-frame capture mode */
+	ceu_write(ceudev, CEU_CEIER,
+		  ceu_read(ceudev, CEU_CEIER) | CEU_CEIER_MASK);
+
+	ceu_write(ceudev, CEU_CAPCR,
+		  ceu_read(ceudev, CEU_CAPCR) & ~CEU_CAPCR_CTNCP);
+
+	ceu_write(ceudev, CEU_CETCR, CEU_CETCR_MAGIC ^ CEU_CETCR_IGRW);
+
+	if (ceudev->field == V4L2_FIELD_INTERLACED_BT) {
+		top1	= CEU_CDBYR;
+		top2	= CEU_CDBCR;
+		bottom1	= CEU_CDAYR;
+		bottom2	= CEU_CDACR;
+	} else {
+		top1	= CEU_CDAYR;
+		top2	= CEU_CDACR;
+		bottom1	= CEU_CDBYR;
+		bottom2	= CEU_CDBCR;
+	}
+
+	phys_addr_top = vb2_dma_contig_plane_dma_addr(&ceudev->active->vb2_buf,
+						      0);
+
+	ceu_write(ceudev, top1, phys_addr_top);
+	if (ceudev->field != V4L2_FIELD_NONE) {
+		phys_addr_bottom = phys_addr_top + pix->bytesperline;
+		ceu_write(ceudev, bottom1, phys_addr_bottom);
+	}
+
+	/* FIXME: this works only for planar formats */
+	phys_addr_top += pix->bytesperline * pix->height;
+	ceu_write(ceudev, top2, phys_addr_top);
+	if (ceudev->field != V4L2_FIELD_NONE) {
+		phys_addr_bottom = phys_addr_top +
+				   pix->bytesperline;
+		ceu_write(ceudev, bottom2, phys_addr_bottom);
+	}
+
+	/*
+	 * Trigger new capture start: once per each frame, as we work in
+	 * one-frame capture mode
+	 */
+	ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CE);
+
+	return ret;
+}
+
+static irqreturn_t ceu_irq(int irq, void *data)
+{
+	struct ceu_device *ceudev = data;
+	struct vb2_v4l2_buffer *vbuf;
+	struct ceu_buffer *buf;
+	int ret;
+
+	spin_lock(&ceudev->lock);
+
+	vbuf = ceudev->active;
+	if (!vbuf)
+		/* Stale interrupt from a released buffer */
+		goto out;
+
+	buf = vb2_to_ceu(vbuf);
+	vbuf->vb2_buf.timestamp = ktime_get_ns();
+	list_del_init(&buf->queue);
+
+	if (!list_empty(&ceudev->capture)) {
+		buf = list_first_entry(&ceudev->capture, struct ceu_buffer,
+				       queue);
+		list_del(&buf->queue);
+		ceudev->active = &buf->vb;
+	} else
+		ceudev->active = NULL;
+
+	ret = ceu_capture(ceudev);
+	if (!ret) {
+		vbuf->field = ceudev->field;
+		vbuf->sequence = ceudev->sequence++;
+	}
+
+	vb2_buffer_done(&vbuf->vb2_buf,
+			ret < 0 ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
+
+out:
+	spin_unlock(&ceudev->lock);
+
+	return IRQ_HANDLED;
+}
+
+/* ----------------------------------------------------------------------------
+ * CEU Videobuf operations
+ */
+
+/*
+ * ceu_videobuf_setup() - is called to check, whether the driver can accept the
+ *			  requested number of buffers and to fill in plane sizes
+ *			  for the current frame format if required
+ */
+static int ceu_videobuf_setup(struct vb2_queue *vq, unsigned int *count,
+			      unsigned int *num_planes, unsigned int sizes[],
+			      struct device *alloc_devs[])
+{
+	struct ceu_device *ceudev = vb2_get_drv_priv(vq);
+	struct v4l2_pix_format *pix = &ceudev->v4l2_pix;
+
+	if (!vq->num_buffers)
+		ceudev->sequence = 0;
+
+	if (!*count)
+		*count = 2;
+
+	/* Called from VIDIOC_REQBUFS or in compatibility mode */
+	if (!*num_planes)
+		sizes[0] = pix->sizeimage;
+	else if (sizes[0] < pix->sizeimage)
+		return -EINVAL;
+
+	*num_planes = 1;
+
+	dev_dbg(ceudev->dev, "count=%d, size=%u\n", *count, sizes[0]);
+
+	return 0;
+}
+
+static void ceu_videobuf_queue(struct vb2_buffer *vb)
+{
+	struct ceu_device *ceudev = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct ceu_buffer *buf = vb2_to_ceu(vbuf);
+	unsigned long irqflags;
+	unsigned long size;
+
+	size = ceudev->v4l2_pix.sizeimage;
+
+	if (vb2_plane_size(vb, 0) < size) {
+		dev_err(&ceudev->vdev.dev, "Buffer #%d too small (%lu < %lu)\n",
+			vb->index, vb2_plane_size(vb, 0), size);
+		goto error;
+	}
+
+	vb2_set_plane_payload(vb, 0, size);
+
+	dev_dbg(&ceudev->vdev.dev, "%s (vb=0x%p) 0x%p %lu\n", __func__,
+		vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0));
+
+	spin_lock_irqsave(&ceudev->lock, irqflags);
+	list_add_tail(&buf->queue, &ceudev->capture);
+	spin_unlock_irqrestore(&ceudev->lock, irqflags);
+
+	return;
+
+error:
+	vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+}
+
+static int ceu_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct ceu_device *ceudev = vb2_get_drv_priv(vq);
+	struct ceu_buffer *buf;
+	unsigned long irqflags;
+	int ret = 0;
+
+	ret = v4l2_subdev_call(ceudev->sensor, video, s_stream, 1);
+	if (ret && ret != -ENOIOCTLCMD) {
+		v4l2_err(&ceudev->v4l2_dev, "stream on failed in subdev\n");
+		goto error_return_bufs;
+	}
+
+	spin_lock_irqsave(&ceudev->lock, irqflags);
+	ceudev->sequence = 0;
+
+	if (ceudev->active) {
+		ret = -EINVAL;
+		spin_unlock_irq(&ceudev->lock);
+		goto error_stop_sensor;
+	}
+
+	buf = list_first_entry(&ceudev->capture, struct ceu_buffer,
+			       queue);
+	list_del(&buf->queue);
+
+	ceudev->active = &buf->vb;
+	ret = ceu_capture(ceudev);
+	if (ret) {
+		spin_unlock_irqrestore(&ceudev->lock, irqflags);
+		goto error_stop_sensor;
+	}
+
+	spin_unlock_irqrestore(&ceudev->lock, irqflags);
+
+	return 0;
+
+error_stop_sensor:
+	v4l2_subdev_call(ceudev->sensor, video, s_stream, 0);
+
+error_return_bufs:
+	spin_lock_irqsave(&ceudev->lock, irqflags);
+	list_for_each_entry(buf, &ceudev->capture, queue)
+		vb2_buffer_done(&ceudev->active->vb2_buf,
+				VB2_BUF_STATE_QUEUED);
+	ceudev->active = NULL;
+	spin_unlock_irqrestore(&ceudev->lock, irqflags);
+
+	return ret;
+}
+
+static void ceu_stop_streaming(struct vb2_queue *vq)
+{
+	struct ceu_device *ceudev = vb2_get_drv_priv(vq);
+	struct ceu_buffer *buf;
+	unsigned long irqflags;
+
+	v4l2_subdev_call(ceudev->sensor, video, s_stream, 0);
+
+	spin_lock_irqsave(&ceudev->lock, irqflags);
+	if (ceudev->active) {
+		vb2_buffer_done(&ceudev->active->vb2_buf,
+				VB2_BUF_STATE_ERROR);
+		ceudev->active = NULL;
+	}
+
+	/* Release all queued buffers */
+	list_for_each_entry(buf, &ceudev->capture, queue)
+		vb2_buffer_done(&ceudev->active->vb2_buf,
+				VB2_BUF_STATE_ERROR);
+	INIT_LIST_HEAD(&ceudev->capture);
+
+	spin_unlock_irqrestore(&ceudev->lock, irqflags);
+
+	/* FIXME: do we really need to reset the interface here? */
+	ceu_soft_reset(ceudev);
+}
+
+static const struct vb2_ops ceu_videobuf_ops = {
+	.queue_setup	= ceu_videobuf_setup,
+	.buf_queue	= ceu_videobuf_queue,
+	.wait_prepare	= vb2_ops_wait_prepare,
+	.wait_finish	= vb2_ops_wait_finish,
+	.start_streaming = ceu_start_streaming,
+	.stop_streaming	= ceu_stop_streaming,
+};
+/**
+ * ----------------------------------------------------------------------------
+ *  CEU bus operations
+ */
+static unsigned int ceu_mbus_config_compatible(
+		const struct v4l2_mbus_config *cfg,
+		unsigned int ceu_host_flags)
+{
+	unsigned int common_flags = cfg->flags & ceu_host_flags;
+	bool hsync, vsync, pclk, data, mode;
+
+	switch (cfg->type) {
+	case V4L2_MBUS_PARALLEL:
+		hsync = common_flags & (V4L2_MBUS_HSYNC_ACTIVE_HIGH |
+					V4L2_MBUS_HSYNC_ACTIVE_LOW);
+		vsync = common_flags & (V4L2_MBUS_VSYNC_ACTIVE_HIGH |
+					V4L2_MBUS_VSYNC_ACTIVE_LOW);
+		pclk = common_flags & (V4L2_MBUS_PCLK_SAMPLE_RISING |
+				       V4L2_MBUS_PCLK_SAMPLE_FALLING);
+		data = common_flags & (V4L2_MBUS_DATA_ACTIVE_HIGH |
+				       V4L2_MBUS_DATA_ACTIVE_LOW);
+		mode = common_flags & (V4L2_MBUS_MASTER | V4L2_MBUS_SLAVE);
+		break;
+	default:
+		return 0;
+	}
+
+	return (hsync && vsync && pclk && data && mode) ? common_flags : 0;
+}
+/**
+ * ceu_test_mbus_param() - test bus parameters against sensor provided ones.
+ *
+ * Return < 0 for error, 0 if g_mbus_config is not supported,
+ * flags > 0  otherwise
+ */
+static int ceu_test_mbus_param(struct ceu_device *ceudev)
+{
+	struct v4l2_subdev *sd = ceudev->sensor;
+	unsigned long common_flags = CEU_BUS_FLAGS;
+	struct v4l2_mbus_config cfg = {
+		.type = V4L2_MBUS_PARALLEL,
+	};
+	int ret;
+
+	ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg);
+	if (ret < 0 && ret == -ENOIOCTLCMD)
+		return ret;
+	else if (ret == -ENOIOCTLCMD)
+		return 0;
+
+	common_flags = ceu_mbus_config_compatible(&cfg, common_flags);
+	if (!common_flags)
+		return -EINVAL;
+
+	return common_flags;
+}
+
+/*
+ * ceu_set_sizes() - compute register settings to accommodate image sizes
+ *
+ * This is a very simplified version of "sh_mobile_ceu_set_rect function
+ * found in the original soc_camera driver
+ */
+static void ceu_set_sizes(struct ceu_device *ceudev)
+{
+	struct v4l2_pix_format *pix = &ceudev->v4l2_pix;
+	unsigned int capwr_hwidth, capwr_vwidth;
+	unsigned int left_offset, top_offset;
+	unsigned int height, width;
+	unsigned int cdwdr_width;
+	u32 camor;
+
+	/* TODO: make these offsets configurable.
+	 * These are used to configure CAMOR, that wants to know the number of
+	 * blanks between a VS/HS signal and valid data.
+	 * This value should come from the sensor, how should we retrieve it?
+	 */
+	left_offset = 0;
+	top_offset = 0;
+
+	width = pix->width;
+	height = pix->height;
+
+	/* Configure CAPWR: length of horizontal/vertical capture period */
+	/* TODO:
+	 * vertical width depends on ceudev->field value? Is this interlaced?
+	 */
+	capwr_vwidth = height;
+
+	/* TODO: make sure this is correct:
+	 * horizontal width depends on macropixel size (bpp / 8);
+	 * a VGA (640x480) image in yuv422 input format has a line length of
+	 * (640 * 16 / 8) = 1280 bytes
+	 */
+	capwr_hwidth = pix->bytesperline;
+
+	/* FIXME: not sure what the original driver does here */
+	cdwdr_width = pix->bytesperline;
+
+	/* Set CAMOR, CAPWR, CFSZR, take care of CDWDR */
+	camor = left_offset | (top_offset << 16);
+	ceu_write(ceudev, CEU_CAMOR, camor);
+	ceu_write(ceudev, CEU_CAPWR, (capwr_vwidth << 16) | capwr_hwidth);
+
+	/* CFSZR clipping is applied _after_ the scaling filter (CEU_CFLCR) */
+	ceu_write(ceudev, CEU_CFSZR, (height << 16) | width);
+	ceu_write(ceudev, CEU_CDWDR, cdwdr_width);
+}
+
+/**
+ * ceu_set_bus_params() - Configure CEU interface registers using bus
+ *			  parameters
+ *
+ * FIXME: compared to the original soc-driver set_bus_param is only called
+ * by set_fmt, which cannot be called while actively streaming.
+ * This probably means we do not have to save_reset and restore CAPSR at the
+ * beginning and at the end of this function like the original one did.
+ */
+static int ceu_set_bus_params(struct ceu_device *ceudev)
+{
+	struct ceu_mbus_fmt *sensor_fmt = &ceudev->sensor_fmt;
+	struct v4l2_pix_format *pix = &ceudev->v4l2_pix;
+	unsigned long common_flags = CEU_BUS_FLAGS;
+	struct v4l2_subdev *sd = ceudev->sensor;
+	u32 camcr, caifr, cdocr;
+	int ret;
+
+	struct v4l2_mbus_config cfg = {
+		.type = V4L2_MBUS_PARALLEL,
+	};
+
+	/*
+	 * TODO: if client doesn't implement g_mbus_config, we just use our
+	 * platform data
+	 */
+	common_flags = ceu_test_mbus_param(ceudev);
+	if (common_flags < 0)
+		return common_flags;
+	else if (common_flags == 0)
+		/* TODO: use platform data */
+
+	/*
+	 * Make choices, based on platform preferences if the we can chose
+	 * between multiple alternatives.
+	 */
+	if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) &&
+	    (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) {
+		if (ceudev->mbus_flags & CEU_FLAG_HSYNC_LOW)
+			common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH;
+		else
+			common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW;
+	}
+
+	if ((common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) &&
+	    (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) {
+		if (ceudev->mbus_flags & CEU_FLAG_VSYNC_LOW)
+			common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_HIGH;
+		else
+			common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_LOW;
+	}
+
+	cfg.flags = common_flags;
+	ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg);
+	if (ret < 0 && ret != -ENOIOCTLCMD)
+		return ret;
+
+	/* Start configuring CEU registers */
+	ceu_write(ceudev, CEU_CRCNTR, 0);
+	ceu_write(ceudev, CEU_CRCMPR, 0);
+
+	/* TODO: handle bps > 8 */
+
+	/*
+	 * Configure CAMCR and CDOCR:
+	 * match input components ordering with memory output format and
+	 * handle downsampling to YUV420.
+	 *
+	 * If the memory output format is swapped (Cr before Cb) and input
+	 * format is not, use the swapped version of CAMCR.DTARY.
+	 *
+	 * If the memory output format is not swapped (Cb before Cr) and
+	 * input format is, use the swapped version of CAMCR.DTARY.
+	 *
+	 * CEU by default downsample to planar YUV420 (CDCOR[4] = 0).
+	 * If output is planar YUV422 set CDOCR[4] = 1
+	 */
+	cdocr = 0;
+	camcr = 0;
+	switch (pix->pixelformat) {
+	/* non-swapped output formats */
+	case V4L2_PIX_FMT_NV16:
+		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
+	case V4L2_PIX_FMT_NV12:
+		if (sensor_fmt->swapped)
+			camcr |= sensor_fmt->fmt_order_swap;
+		else
+			camcr |= sensor_fmt->fmt_order;
+		break;
+
+	/* swapped output formats */
+	case V4L2_PIX_FMT_NV61:
+		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
+	case V4L2_PIX_FMT_NV21:
+		if (sensor_fmt->swapped)
+			camcr |= sensor_fmt->fmt_order;
+		else
+			camcr |= sensor_fmt->fmt_order_swap;
+		break;
+	}
+	camcr |= common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW ? 1 << 1 : 0;
+	camcr |= common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW ? 1 << 0 : 0;
+
+	/* TODO: handle 16 bit bus width; use 8 bits by default now */
+	camcr |= CEU_CAMCR_DTIF_8BITS;
+	ceu_write(ceudev, CEU_CAMCR, camcr);
+
+	/*
+	 * TODO: handle data swap mode in CDOCR[3:0] for data fetch sync
+	 * before writing CDOCR register
+	 */
+	ceu_write(ceudev, CEU_CDOCR, cdocr);
+
+	ceu_write(ceudev, CEU_CAPCR, CEU_CAPCR_BUS_WIDTH256);
+
+	/*
+	 * Support for interlaced input source.
+	 * FIXME: this comes from soc-camera driver. Investigate further
+	 */
+	switch (pix->field) {
+	case V4L2_FIELD_INTERLACED_TB:
+		caifr = 0x101;
+		break;
+	case V4L2_FIELD_INTERLACED_BT:
+		caifr = 0x102;
+		break;
+	default:
+		caifr = 0;
+		break;
+	}
+	ceu_write(ceudev, CEU_CAIFR, caifr);
+
+	ceu_set_sizes(ceudev);
+
+	/* TODO: CEU_CFLCR scale down filter not supported yet */
+
+	/* Keep "datafetch firewall" disabled */
+	ceu_write(ceudev, CEU_CFWCR, 0);
+
+	/*
+	 * Not in bundle mode: skip CEU_CBDSR, CEU_CDAYR2, CEU_CDACR2,
+	 * CEU_CDBYR2, CEU_CDBCR2
+	 */
+
+	return 0;
+}
+
+/**
+ * ----------------------------------------------------------------------------
+ *  CEU image formats handling
+ */
+
+/**
+ * ceu_try_fmt() - test format on CEU and sensor
+ *
+ * @v4l2_fmt: format to test
+ */
+static int ceu_try_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt)
+{
+	struct ceu_mbus_fmt *sensor_fmt = &ceudev->sensor_fmt;
+	struct v4l2_pix_format *pix = &v4l2_fmt->fmt.pix;
+	struct v4l2_subdev *sensor = ceudev->sensor;
+	struct v4l2_subdev_pad_config pad_cfg;
+	const struct ceu_fmt *ceu_fmt;
+	int ret;
+
+	struct v4l2_subdev_format sd_format = {
+		.which = V4L2_SUBDEV_FORMAT_TRY,
+	};
+
+	switch (pix->pixelformat) {
+	case V4L2_PIX_FMT_NV16:
+	case V4L2_PIX_FMT_NV61:
+	case V4L2_PIX_FMT_NV12:
+	case V4L2_PIX_FMT_NV21:
+		ceu_fmt = get_ceu_fmt_from_fourcc(pix->pixelformat);
+		break;
+
+	default:
+		/*
+		 * Reject all non-NV[12|21|16|61] pixel formats as we only
+		 * support image fetch mode.
+		 *
+		 * TODO: add support for packed YUYV output formats, binary and
+		 * raw data capture through data fetch sync mode
+		 */
+		dev_err(ceudev->dev, "Pixel format 0x%x not supported\n",
+			pix->pixelformat);
+		return -EINVAL;
+	}
+
+	dev_dbg(ceudev->dev, "Try format: 0x%x - %ux%u\n\n",
+		pix->pixelformat, pix->width, pix->height);
+
+	/* CFSZR requires height and width to be 4-pixel aligned */
+	v4l_bound_align_image(&pix->width, 2, CEU_MAX_WIDTH, 2,
+			      &pix->height, 4, CEU_MAX_HEIGHT, 2, 0);
+
+	/*
+	 * Set format on sensor sub device: bus format is selected at
+	 * format initialization time
+	 */
+	v4l2_fill_mbus_format(&sd_format.format, pix, sensor_fmt->mbus_code);
+	ret = v4l2_subdev_call(sensor, pad, set_fmt, &pad_cfg, &sd_format);
+	if (ret)
+		return ret;
+
+	/* TODO: scale CEU format to match what is returned by subdevice */
+
+	v4l2_fill_pix_format(pix, &sd_format.format);
+	pix->field		= V4L2_FIELD_NONE;
+	pix->bytesperline	= pix->width * ceu_fmt->bpp / 8;
+	pix->sizeimage		= pix->height * pix->bytesperline;
+
+	/* Test bus parameters */
+	ret = ceu_test_mbus_param(ceudev);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/**
+ * ceu_set_fmt() - Apply the supplied format to both sensor and CEU
+ */
+static int ceu_set_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt)
+{
+	struct ceu_mbus_fmt *sensor_fmt = &ceudev->sensor_fmt;
+	int ret;
+
+	struct v4l2_subdev_format format = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+	};
+
+	ret = ceu_try_fmt(ceudev, v4l2_fmt);
+	if (ret)
+		return ret;
+
+	v4l2_fill_mbus_format(&format.format, &v4l2_fmt->fmt.pix,
+			      sensor_fmt->mbus_code);
+	ret = v4l2_subdev_call(ceudev->sensor, pad,
+			       set_fmt, NULL, &format);
+	if (ret)
+		return ret;
+
+	ceudev->v4l2_pix = v4l2_fmt->fmt.pix;
+
+	ret = ceu_set_bus_params(ceudev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/**
+ * ceu_set_default_fmt() - Apply default NV16 memory output format with VGA
+ *			   sizes
+ */
+static int ceu_set_default_fmt(struct ceu_device *ceudev)
+{
+	int ret;
+	struct v4l2_format v4l2_fmt = {
+		.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+		.fmt.pix = {
+			.width		= VGA_WIDTH,
+			.height		= VGA_HEIGHT,
+			.field		= V4L2_FIELD_NONE,
+			.pixelformat	= V4L2_PIX_FMT_NV16,
+		},
+	};
+
+	ret = ceu_try_fmt(ceudev, &v4l2_fmt);
+	if (ret)
+		return ret;
+
+	ceudev->v4l2_pix = v4l2_fmt.fmt.pix;
+
+	return 0;
+}
+
+/**
+ * ceu_init_formats() - Query sensor for supported formats and initialize
+ *			CEU supported format list
+ *
+ * Find out if sensor can produce a permutation of 8-bits YUYV bus format.
+ * From a single 8-bits YUYV bus format the CEU can produce several memory
+ * output formats:
+ * - NV[12|21|16|61] through image fetch mode;
+ * - TODO: Packed YUYV permutations throug data fetch sync mode;
+ * - TODO: Binary data (eg. JPEG) and raw formats. through data fetch sync mode
+ */
+static int ceu_init_formats(struct ceu_device *ceudev)
+{
+	struct ceu_mbus_fmt *sensor_fmt = &ceudev->sensor_fmt;
+	struct v4l2_subdev *sensor = ceudev->sensor;
+	bool yuyv_bus_fmt = false;
+
+	struct v4l2_subdev_mbus_code_enum mbus_fmt = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		.index = 0,
+	};
+
+	/* Find out if sensor can produce any permutation of 8-bits yuyv */
+	while (!yuyv_bus_fmt &&
+	       !v4l2_subdev_call(sensor, pad, enum_mbus_code,
+				 NULL, &mbus_fmt)) {
+		switch (mbus_fmt.code) {
+		case MEDIA_BUS_FMT_YUYV8_2X8:
+		case MEDIA_BUS_FMT_YVYU8_2X8:
+		case MEDIA_BUS_FMT_UYVY8_2X8:
+		case MEDIA_BUS_FMT_VYUY8_2X8:
+			yuyv_bus_fmt = true;
+			break;
+		default:
+			/*
+			 * Only support 8-bits YUYV bus formats at the moment;
+			 *
+			 * TODO: add support for binary formats (data sync
+			 * fetch mode).
+			 */
+			break;
+		}
+	}
+
+	if (!yuyv_bus_fmt)
+		return -ENXIO;
+
+	/*
+	 * Save the first encountered YUYV format as "sensor_fmt" and use it
+	 * to output all planar YUV422 and YUV420 (NV*) formats to memory.
+	 */
+	sensor_fmt->mbus_code	= mbus_fmt.code;
+	sensor_fmt->bps		= 8;
+
+	/* Annotate the selected bus format components ordering */
+	switch (mbus_fmt.code) {
+	case MEDIA_BUS_FMT_YUYV8_2X8:
+		sensor_fmt->fmt_order		= CEU_CAMCR_DTARY_8_YUYV;
+		sensor_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_YVYU;
+		sensor_fmt->swapped		= false;
+		break;
+
+	case MEDIA_BUS_FMT_YVYU8_2X8:
+		sensor_fmt->fmt_order		= CEU_CAMCR_DTARY_8_YVYU;
+		sensor_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_YUYV;
+		sensor_fmt->swapped		= true;
+		break;
+
+	case MEDIA_BUS_FMT_UYVY8_2X8:
+		sensor_fmt->fmt_order		= CEU_CAMCR_DTARY_8_UYVY;
+		sensor_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_VYUY;
+		sensor_fmt->swapped		= false;
+		break;
+
+	case MEDIA_BUS_FMT_VYUY8_2X8:
+		sensor_fmt->fmt_order		= CEU_CAMCR_DTARY_8_VYUY;
+		sensor_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_UYVY;
+		sensor_fmt->swapped		= true;
+		break;
+	}
+
+	ceudev->field = V4L2_FIELD_NONE;
+
+	return 0;
+}
+
+/**
+ * ----------------------------------------------------------------------------
+ *  Runtime PM Handlers
+ */
+
+/**
+ * ceu_runtime_suspend() - disable capture and interrupts and soft-reset.
+ *			   Turn sensor power off.
+ */
+static int ceu_runtime_suspend(struct device *dev)
+{
+	struct ceu_device *ceudev = dev_get_drvdata(dev);
+	struct v4l2_subdev *sensor = ceudev->sensor;
+
+	v4l2_subdev_call(sensor, core, s_power, 0);
+
+	ceu_write(ceudev, CEU_CEIER, 0);
+	ceu_soft_reset(ceudev);
+
+	return 0;
+}
+
+/**
+ * ceu_runtime_resume() - soft-reset the interface and turn sensor power on.
+ */
+static int ceu_runtime_resume(struct device *dev)
+{
+	struct ceu_device *ceudev = dev_get_drvdata(dev);
+	struct v4l2_subdev *sensor = ceudev->sensor;
+
+	v4l2_subdev_call(sensor, core, s_power, 1);
+
+	ceu_soft_reset(ceudev);
+
+	return 0;
+}
+
+/**
+ * ----------------------------------------------------------------------------
+ *  File Operations
+ */
+static int ceu_open(struct file *file)
+{
+	struct ceu_device *ceudev = video_drvdata(file);
+	int ret;
+
+	ret = v4l2_fh_open(file);
+	if (ret)
+		return ret;
+
+	mutex_lock(&ceudev->mlock);
+	/* Causes soft-reset and sensor power on on first open */
+	pm_runtime_get_sync(ceudev->dev);
+	mutex_unlock(&ceudev->mlock);
+
+	return 0;
+}
+
+static int ceu_release(struct file *file)
+{
+	struct ceu_device *ceudev = video_drvdata(file);
+
+	vb2_fop_release(file);
+
+	mutex_lock(&ceudev->mlock);
+	/* Causes soft-reset and sensor power down on last close */
+	pm_runtime_put(ceudev->dev);
+	mutex_unlock(&ceudev->mlock);
+
+	return 0;
+}
+
+static const struct v4l2_file_operations ceu_fops = {
+	.owner			= THIS_MODULE,
+	.open			= ceu_open,
+	.release		= ceu_release,
+	.unlocked_ioctl		= video_ioctl2,
+	.read			= vb2_fop_read,
+	.mmap			= vb2_fop_mmap,
+	.poll			= vb2_fop_poll,
+};
+
+/**
+ * ----------------------------------------------------------------------------
+ *  Video Device IOCTLs
+ */
+static int ceu_querycap(struct file *file, void *priv,
+			struct v4l2_capability *cap)
+{
+	strlcpy(cap->card, "Renesas-CEU", sizeof(cap->card));
+	strlcpy(cap->driver, DRIVER_NAME, sizeof(cap->driver));
+	strlcpy(cap->bus_info, "platform:renesas-ceu", sizeof(cap->bus_info));
+
+	return 0;
+}
+
+static int ceu_enum_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_fmtdesc *f)
+{
+	const struct ceu_fmt *fmt;
+
+	if (f->index >= ARRAY_SIZE(ceu_fmt_list) - 1)
+		return -EINVAL;
+
+	fmt = &ceu_fmt_list[f->index];
+	f->pixelformat = fmt->fourcc;
+
+	return 0;
+}
+
+static int ceu_try_fmt_vid_cap(struct file *file, void *priv,
+			       struct v4l2_format *f)
+{
+	struct ceu_device *ceudev = video_drvdata(file);
+
+	return ceu_try_fmt(ceudev, f);
+}
+
+static int ceu_s_fmt_vid_cap(struct file *file, void *priv,
+			     struct v4l2_format *f)
+{
+	struct ceu_device *ceudev = video_drvdata(file);
+
+	if (vb2_is_streaming(&ceudev->vb2_vq))
+		return -EBUSY;
+
+	return ceu_set_fmt(ceudev, f);
+}
+
+static int ceu_enum_input(struct file *file, void *priv,
+			  struct v4l2_input *inp)
+{
+	if (inp->index != 0)
+		return -EINVAL;
+
+	inp->type = V4L2_INPUT_TYPE_CAMERA;
+	inp->std = 0;
+	strcpy(inp->name, "Camera");
+
+	return 0;
+}
+
+static int ceu_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	*i = 0;
+
+	return 0;
+}
+
+static int ceu_s_input(struct file *file, void *priv, unsigned int i)
+{
+	if (i > 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int ceu_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+	struct ceu_device *ceudev = video_drvdata(file);
+
+	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	return v4l2_subdev_call(ceudev->sensor, video, g_parm, a);
+}
+
+static int ceu_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+	struct ceu_device *ceudev = video_drvdata(file);
+
+	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	return v4l2_subdev_call(ceudev->sensor, video, s_parm, a);
+}
+
+static int ceu_enum_framesizes(struct file *file, void *fh,
+			       struct v4l2_frmsizeenum *fsize)
+{
+	struct ceu_device *ceudev = video_drvdata(file);
+	int ret;
+
+	struct v4l2_subdev_frame_size_enum fse = {
+		.code	= ceudev->sensor_fmt.mbus_code,
+		.index	= fsize->index,
+		.which	= V4L2_SUBDEV_FORMAT_ACTIVE,
+	};
+
+	ret = v4l2_subdev_call(ceudev->sensor, pad, enum_frame_size,
+			       NULL, &fse);
+	if (ret)
+		return ret;
+
+	fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+	fsize->discrete.width = CEU_W_MAX(fse.max_width);
+	fsize->discrete.height = CEU_H_MAX(fse.max_height);
+
+	return 0;
+}
+
+static int ceu_enum_frameintervals(struct file *file, void *fh,
+				    struct v4l2_frmivalenum *fival)
+{
+	struct ceu_device *ceudev = video_drvdata(file);
+	int ret;
+
+	struct v4l2_subdev_frame_interval_enum fie = {
+		.code	= ceudev->sensor_fmt.mbus_code,
+		.index = fival->index,
+		.width = fival->width,
+		.height = fival->height,
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+	};
+
+	ret = v4l2_subdev_call(ceudev->sensor, pad, enum_frame_interval, NULL,
+			       &fie);
+	if (ret)
+		return ret;
+
+	fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+	fival->discrete = fie.interval;
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops ceu_ioctl_ops = {
+	.vidioc_querycap		= ceu_querycap,
+
+	.vidioc_enum_fmt_vid_cap	= ceu_enum_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap		= ceu_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap		= ceu_s_fmt_vid_cap,
+
+	.vidioc_enum_input		= ceu_enum_input,
+	.vidioc_g_input			= ceu_g_input,
+	.vidioc_s_input			= ceu_s_input,
+
+	.vidioc_reqbufs			= vb2_ioctl_reqbufs,
+	.vidioc_querybuf		= vb2_ioctl_querybuf,
+	.vidioc_qbuf			= vb2_ioctl_qbuf,
+	.vidioc_expbuf			= vb2_ioctl_expbuf,
+	.vidioc_dqbuf			= vb2_ioctl_dqbuf,
+	.vidioc_create_bufs		= vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf		= vb2_ioctl_prepare_buf,
+	.vidioc_streamon		= vb2_ioctl_streamon,
+	.vidioc_streamoff		= vb2_ioctl_streamoff,
+
+	.vidioc_g_parm			= ceu_g_parm,
+	.vidioc_s_parm			= ceu_s_parm,
+	.vidioc_enum_framesizes		= ceu_enum_framesizes,
+	.vidioc_enum_frameintervals	= ceu_enum_frameintervals,
+};
+
+/**
+ * ceu_vdev_release() - release CEU video device memory when last reference
+ *			to this driver is closed
+ */
+void ceu_vdev_release(struct video_device *vdev)
+{
+	struct ceu_device *ceudev = video_get_drvdata(vdev);
+
+	kfree(ceudev);
+}
+
+static int ceu_sensor_bound(struct v4l2_async_notifier *notifier,
+			    struct v4l2_subdev *subdev,
+			    struct v4l2_async_subdev *asd)
+{
+	struct v4l2_device *v4l2_dev = notifier->v4l2_dev;
+	struct ceu_device *ceudev = v4l2_to_ceu(v4l2_dev);
+	struct video_device *vdev = &ceudev->vdev;
+	struct vb2_queue *q = &ceudev->vb2_vq;
+	int ret;
+
+	/* Initialize vb2 queue */
+	q->type			= V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	q->io_modes		= VB2_MMAP | VB2_USERPTR;
+	q->drv_priv		= ceudev;
+	q->ops			= &ceu_videobuf_ops;
+	q->mem_ops		= &vb2_dma_contig_memops;
+	q->buf_struct_size	= sizeof(struct ceu_buffer);
+	q->timestamp_flags	= V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	q->lock			= &ceudev->mlock;
+	q->dev			= ceudev->v4l2_dev.dev;
+
+	ret = vb2_queue_init(q);
+	if (ret)
+		return ret;
+
+	/* Initialize formats and set default format on sensor */
+	ceudev->sensor = subdev;
+	ret = ceu_init_formats(ceudev);
+	if (ret)
+		return ret;
+
+	ret = ceu_set_default_fmt(ceudev);
+	if (ret)
+		return ret;
+
+	/* Register the video device */
+	strncpy(vdev->name, DRIVER_NAME, strlen(DRIVER_NAME));
+	vdev->v4l2_dev		= v4l2_dev;
+	vdev->lock		= &ceudev->mlock;
+	vdev->queue		= &ceudev->vb2_vq;
+	vdev->ctrl_handler	= subdev->ctrl_handler;
+	vdev->fops		= &ceu_fops;
+	vdev->ioctl_ops		= &ceu_ioctl_ops;
+	vdev->release		= ceu_vdev_release;
+	vdev->device_caps	= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+	video_set_drvdata(vdev, ceudev);
+
+	ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+	if (ret < 0) {
+		v4l2_err(vdev->v4l2_dev,
+			 "video_register_device failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ceu_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct ceu_device *ceudev;
+	struct resource *res;
+	void __iomem *base;
+	unsigned int irq;
+	int ret;
+
+	ceudev = kzalloc(sizeof(*ceudev), GFP_KERNEL);
+	if (!ceudev)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, ceudev);
+	dev_set_drvdata(dev, ceudev);
+	ceudev->dev = dev;
+
+	INIT_LIST_HEAD(&ceudev->capture);
+	spin_lock_init(&ceudev->lock);
+	mutex_init(&ceudev->mlock);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+	ceudev->base = base;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(dev, "failed to get irq: %d\n", ret);
+		return irq;
+	}
+
+	ret = devm_request_irq(dev, irq, ceu_irq,
+			       0, dev_name(dev), ceudev);
+	if (ret) {
+		dev_err(&pdev->dev, "Unable to register CEU interrupt.\n");
+		return ret;
+	}
+
+	if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
+		/* TODO: implement OF parsing */
+	} else if (dev->platform_data) {
+		ceudev->pdata = dev->platform_data;
+		ceudev->mbus_flags = ceudev->pdata->flags;
+		ceudev->asd.match_type = V4L2_ASYNC_MATCH_I2C;
+		ceudev->asd.match.i2c.adapter_id =
+			ceudev->pdata->sensor_i2c_adapter_id;
+		ceudev->asd.match.i2c.address =
+			ceudev->pdata->sensor_i2c_address;
+	} else {
+		dev_err(&pdev->dev, "CEU platform data not set.\n");
+		return -EINVAL;
+	}
+
+	pm_suspend_ignore_children(dev, true);
+	pm_runtime_enable(dev);
+
+	ret = v4l2_device_register(dev, &ceudev->v4l2_dev);
+	if (ret)
+		goto error_pm_disable;
+
+	ceudev->asds[0]			= &ceudev->asd;
+	ceudev->notifier.v4l2_dev	= &ceudev->v4l2_dev;
+	ceudev->notifier.subdevs	= ceudev->asds;
+	ceudev->notifier.num_subdevs	= 1;
+	ceudev->notifier.bound		= ceu_sensor_bound;
+	ret = v4l2_async_notifier_register(&ceudev->v4l2_dev,
+					   &ceudev->notifier);
+	if (ret)
+		goto error_v4l2_unregister;
+
+	return 0;
+
+error_v4l2_unregister:
+	v4l2_device_unregister(&ceudev->v4l2_dev);
+error_pm_disable:
+	pm_runtime_disable(dev);
+
+	return ret;
+}
+
+static int ceu_remove(struct platform_device *pdev)
+{
+	struct ceu_device *ceudev = platform_get_drvdata(pdev);
+
+	pm_runtime_disable(ceudev->dev);
+
+	v4l2_async_notifier_unregister(&ceudev->notifier);
+	v4l2_device_unregister(&ceudev->v4l2_dev);
+
+	video_unregister_device(&ceudev->vdev);
+
+	return 0;
+}
+
+#if IS_ENABLED(CONFIG_OF)
+static const struct of_device_id ceu_of_match[] = {
+	/* FIXME: find a better "compatible" string, maybe */
+	{ .compatible = "renesas,renesas-ceu" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ceu_of_match);
+#endif
+
+static const struct dev_pm_ops ceu_pm_ops = {
+	SET_RUNTIME_PM_OPS(ceu_runtime_suspend,
+			   ceu_runtime_resume,
+			   NULL)
+};
+
+static struct platform_driver ceu_driver = {
+	.driver		= {
+		.name	= DRIVER_NAME,
+		.pm	= &ceu_pm_ops,
+		.of_match_table = of_match_ptr(ceu_of_match),
+	},
+	.probe		= ceu_probe,
+	.remove		= ceu_remove,
+};
+
+module_platform_driver(ceu_driver);
+
+MODULE_DESCRIPTION("Renesas CEU camera driver");
+MODULE_AUTHOR("Jacopo Mondi <jacopo+renesas@jmondi.org>");
+MODULE_LICENSE("GPL");
--
2.7.4

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC v4 4/8] media: platform: ceu: Support for multiple subdevs
  2017-05-19 16:02 [RFC v4 0/8] Renesas CEU driver Jacopo Mondi
                   ` (2 preceding siblings ...)
  2017-05-19 16:02 ` [RFC v4 3/8] media: platform: Add Renesas " Jacopo Mondi
@ 2017-05-19 16:02 ` Jacopo Mondi
  2017-05-19 16:02 ` [RFC v4 5/8] media: i2c: ov772x: Force use of SCCB protocol Jacopo Mondi
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Jacopo Mondi @ 2017-05-19 16:02 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm; +Cc: linux-renesas-soc

Add support for registration of multiple subdevices to renesas CEU
driver.

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
---
 drivers/media/platform/renesas-ceu.c | 323 +++++++++++++++++++++++++----------
 1 file changed, 235 insertions(+), 88 deletions(-)

diff --git a/drivers/media/platform/renesas-ceu.c b/drivers/media/platform/renesas-ceu.c
index 8ebc2547..05cb1fc 100644
--- a/drivers/media/platform/renesas-ceu.c
+++ b/drivers/media/platform/renesas-ceu.c
@@ -174,6 +174,27 @@ static inline struct ceu_buffer *vb2_to_ceu(struct vb2_v4l2_buffer *vbuf)
 }

 /**
+ * ceu_subdev - Wraps v4l2 sub-device and provides async notification
+ */
+struct ceu_subdev {
+	struct v4l2_subdev *v4l2_subdev;
+
+	/* async subdev notification */
+	struct v4l2_async_notifier notifier;
+	struct v4l2_async_subdev *asd;
+
+	unsigned int mbus_flags;
+	struct ceu_mbus_fmt mbus_fmt;
+
+	struct list_head list;
+};
+
+static inline struct ceu_subdev *to_ceu_subdev(struct v4l2_async_notifier *n)
+{
+	return container_of(n, struct ceu_subdev, notifier);
+}
+
+/**
  * ceu_device - CEU device instance
  */
 struct ceu_device {
@@ -181,12 +202,12 @@ struct ceu_device {
 	struct video_device	vdev;
 	struct v4l2_device	v4l2_dev;

-	struct v4l2_subdev	*sensor;
-	struct ceu_mbus_fmt	sensor_fmt;
-
-	struct v4l2_async_notifier	notifier;
-	struct v4l2_async_subdev	asd;
-	struct v4l2_async_subdev	*asds[1];
+	/* subdevs list, number and current subdev */
+	struct list_head	subdev_list;
+	unsigned int		num_subdevs;
+	struct ceu_subdev	*subdevs;
+	struct ceu_subdev	*subdev;
+	unsigned int		subdev_index;

 	/* vb2 queue, capture buffer list and active buffer pointer */
 	struct vb2_queue	vb2_vq;
@@ -205,7 +226,6 @@ struct ceu_device {
 	enum v4l2_field		field;
 	struct v4l2_pix_format	v4l2_pix;

-	unsigned long		mbus_flags;
 	struct ceu_info		*pdata;
 };

@@ -518,11 +538,12 @@ static void ceu_videobuf_queue(struct vb2_buffer *vb)
 static int ceu_start_streaming(struct vb2_queue *vq, unsigned int count)
 {
 	struct ceu_device *ceudev = vb2_get_drv_priv(vq);
+	struct v4l2_subdev *v4l2_sd = ceudev->subdev->v4l2_subdev;
 	struct ceu_buffer *buf;
 	unsigned long irqflags;
 	int ret = 0;

-	ret = v4l2_subdev_call(ceudev->sensor, video, s_stream, 1);
+	ret = v4l2_subdev_call(v4l2_sd, video, s_stream, 1);
 	if (ret && ret != -ENOIOCTLCMD) {
 		v4l2_err(&ceudev->v4l2_dev, "stream on failed in subdev\n");
 		goto error_return_bufs;
@@ -553,7 +574,7 @@ static int ceu_start_streaming(struct vb2_queue *vq, unsigned int count)
 	return 0;

 error_stop_sensor:
-	v4l2_subdev_call(ceudev->sensor, video, s_stream, 0);
+	v4l2_subdev_call(v4l2_sd, video, s_stream, 0);

 error_return_bufs:
 	spin_lock_irqsave(&ceudev->lock, irqflags);
@@ -569,10 +590,11 @@ static int ceu_start_streaming(struct vb2_queue *vq, unsigned int count)
 static void ceu_stop_streaming(struct vb2_queue *vq)
 {
 	struct ceu_device *ceudev = vb2_get_drv_priv(vq);
+	struct v4l2_subdev *v4l2_sd = ceudev->subdev->v4l2_subdev;
 	struct ceu_buffer *buf;
 	unsigned long irqflags;

-	v4l2_subdev_call(ceudev->sensor, video, s_stream, 0);
+	v4l2_subdev_call(v4l2_sd, video, s_stream, 0);

 	spin_lock_irqsave(&ceudev->lock, irqflags);
 	if (ceudev->active) {
@@ -638,14 +660,14 @@ static unsigned int ceu_mbus_config_compatible(
  */
 static int ceu_test_mbus_param(struct ceu_device *ceudev)
 {
-	struct v4l2_subdev *sd = ceudev->sensor;
+	struct v4l2_subdev *v4l2_sd = ceudev->subdev->v4l2_subdev;
 	unsigned long common_flags = CEU_BUS_FLAGS;
 	struct v4l2_mbus_config cfg = {
 		.type = V4L2_MBUS_PARALLEL,
 	};
 	int ret;

-	ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg);
+	ret = v4l2_subdev_call(v4l2_sd, video, g_mbus_config, &cfg);
 	if (ret < 0 && ret == -ENOIOCTLCMD)
 		return ret;
 	else if (ret == -ENOIOCTLCMD)
@@ -721,10 +743,12 @@ static void ceu_set_sizes(struct ceu_device *ceudev)
  */
 static int ceu_set_bus_params(struct ceu_device *ceudev)
 {
-	struct ceu_mbus_fmt *sensor_fmt = &ceudev->sensor_fmt;
+	struct ceu_subdev *ceu_sd = ceudev->subdev;
+	struct ceu_mbus_fmt *mbus_fmt = &ceu_sd->mbus_fmt;
+	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_subdev;
+	unsigned int mbus_flags = ceu_sd->mbus_flags;
 	struct v4l2_pix_format *pix = &ceudev->v4l2_pix;
 	unsigned long common_flags = CEU_BUS_FLAGS;
-	struct v4l2_subdev *sd = ceudev->sensor;
 	u32 camcr, caifr, cdocr;
 	int ret;

@@ -748,7 +772,7 @@ static int ceu_set_bus_params(struct ceu_device *ceudev)
 	 */
 	if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) &&
 	    (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) {
-		if (ceudev->mbus_flags & CEU_FLAG_HSYNC_LOW)
+		if (mbus_flags & CEU_FLAG_HSYNC_LOW)
 			common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH;
 		else
 			common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW;
@@ -756,14 +780,14 @@ static int ceu_set_bus_params(struct ceu_device *ceudev)

 	if ((common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) &&
 	    (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) {
-		if (ceudev->mbus_flags & CEU_FLAG_VSYNC_LOW)
+		if (mbus_flags & CEU_FLAG_VSYNC_LOW)
 			common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_HIGH;
 		else
 			common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_LOW;
 	}

 	cfg.flags = common_flags;
-	ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg);
+	ret = v4l2_subdev_call(v4l2_sd, video, s_mbus_config, &cfg);
 	if (ret < 0 && ret != -ENOIOCTLCMD)
 		return ret;

@@ -794,20 +818,20 @@ static int ceu_set_bus_params(struct ceu_device *ceudev)
 	case V4L2_PIX_FMT_NV16:
 		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
 	case V4L2_PIX_FMT_NV12:
-		if (sensor_fmt->swapped)
-			camcr |= sensor_fmt->fmt_order_swap;
+		if (mbus_fmt->swapped)
+			camcr |= mbus_fmt->fmt_order_swap;
 		else
-			camcr |= sensor_fmt->fmt_order;
+			camcr |= mbus_fmt->fmt_order;
 		break;

 	/* swapped output formats */
 	case V4L2_PIX_FMT_NV61:
 		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
 	case V4L2_PIX_FMT_NV21:
-		if (sensor_fmt->swapped)
-			camcr |= sensor_fmt->fmt_order;
+		if (mbus_fmt->swapped)
+			camcr |= mbus_fmt->fmt_order;
 		else
-			camcr |= sensor_fmt->fmt_order_swap;
+			camcr |= mbus_fmt->fmt_order_swap;
 		break;
 	}
 	camcr |= common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW ? 1 << 1 : 0;
@@ -869,9 +893,10 @@ static int ceu_set_bus_params(struct ceu_device *ceudev)
  */
 static int ceu_try_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt)
 {
-	struct ceu_mbus_fmt *sensor_fmt = &ceudev->sensor_fmt;
+	struct ceu_subdev *ceu_sd = ceudev->subdev;
+	struct ceu_mbus_fmt *mbus_fmt = &ceu_sd->mbus_fmt;
+	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_subdev;
 	struct v4l2_pix_format *pix = &v4l2_fmt->fmt.pix;
-	struct v4l2_subdev *sensor = ceudev->sensor;
 	struct v4l2_subdev_pad_config pad_cfg;
 	const struct ceu_fmt *ceu_fmt;
 	int ret;
@@ -912,8 +937,8 @@ static int ceu_try_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt)
 	 * Set format on sensor sub device: bus format is selected at
 	 * format initialization time
 	 */
-	v4l2_fill_mbus_format(&sd_format.format, pix, sensor_fmt->mbus_code);
-	ret = v4l2_subdev_call(sensor, pad, set_fmt, &pad_cfg, &sd_format);
+	v4l2_fill_mbus_format(&sd_format.format, pix, mbus_fmt->mbus_code);
+	ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, &pad_cfg, &sd_format);
 	if (ret)
 		return ret;

@@ -937,7 +962,9 @@ static int ceu_try_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt)
  */
 static int ceu_set_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt)
 {
-	struct ceu_mbus_fmt *sensor_fmt = &ceudev->sensor_fmt;
+	struct ceu_subdev *ceu_sd = ceudev->subdev;
+	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_subdev;
+	struct ceu_mbus_fmt *mbus_fmt = &ceu_sd->mbus_fmt;
 	int ret;

 	struct v4l2_subdev_format format = {
@@ -949,9 +976,8 @@ static int ceu_set_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt)
 		return ret;

 	v4l2_fill_mbus_format(&format.format, &v4l2_fmt->fmt.pix,
-			      sensor_fmt->mbus_code);
-	ret = v4l2_subdev_call(ceudev->sensor, pad,
-			       set_fmt, NULL, &format);
+			      mbus_fmt->mbus_code);
+	ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, NULL, &format);
 	if (ret)
 		return ret;

@@ -1003,20 +1029,21 @@ static int ceu_set_default_fmt(struct ceu_device *ceudev)
  */
 static int ceu_init_formats(struct ceu_device *ceudev)
 {
-	struct ceu_mbus_fmt *sensor_fmt = &ceudev->sensor_fmt;
-	struct v4l2_subdev *sensor = ceudev->sensor;
+	struct ceu_subdev *ceu_sd = ceudev->subdev;
+	struct ceu_mbus_fmt *mbus_fmt = &ceu_sd->mbus_fmt;
+	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_subdev;
 	bool yuyv_bus_fmt = false;

-	struct v4l2_subdev_mbus_code_enum mbus_fmt = {
+	struct v4l2_subdev_mbus_code_enum sd_mbus_fmt = {
 		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
 		.index = 0,
 	};

 	/* Find out if sensor can produce any permutation of 8-bits yuyv */
 	while (!yuyv_bus_fmt &&
-	       !v4l2_subdev_call(sensor, pad, enum_mbus_code,
-				 NULL, &mbus_fmt)) {
-		switch (mbus_fmt.code) {
+	       !v4l2_subdev_call(v4l2_sd, pad, enum_mbus_code,
+				 NULL, &sd_mbus_fmt)) {
+		switch (sd_mbus_fmt.code) {
 		case MEDIA_BUS_FMT_YUYV8_2X8:
 		case MEDIA_BUS_FMT_YVYU8_2X8:
 		case MEDIA_BUS_FMT_UYVY8_2X8:
@@ -1037,37 +1064,37 @@ static int ceu_init_formats(struct ceu_device *ceudev)
 	if (!yuyv_bus_fmt)
 		return -ENXIO;

-	/*
-	 * Save the first encountered YUYV format as "sensor_fmt" and use it
+	/*
+	 * Save the first encountered YUYV format as "mbus_fmt" and use it
 	 * to output all planar YUV422 and YUV420 (NV*) formats to memory.
 	 */
-	sensor_fmt->mbus_code	= mbus_fmt.code;
-	sensor_fmt->bps		= 8;
+	mbus_fmt->mbus_code	= sd_mbus_fmt.code;
+	mbus_fmt->bps		= 8;

 	/* Annotate the selected bus format components ordering */
-	switch (mbus_fmt.code) {
+	switch (sd_mbus_fmt.code) {
 	case MEDIA_BUS_FMT_YUYV8_2X8:
-		sensor_fmt->fmt_order		= CEU_CAMCR_DTARY_8_YUYV;
-		sensor_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_YVYU;
-		sensor_fmt->swapped		= false;
+		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_YUYV;
+		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_YVYU;
+		mbus_fmt->swapped		= false;
 		break;

 	case MEDIA_BUS_FMT_YVYU8_2X8:
-		sensor_fmt->fmt_order		= CEU_CAMCR_DTARY_8_YVYU;
-		sensor_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_YUYV;
-		sensor_fmt->swapped		= true;
+		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_YVYU;
+		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_YUYV;
+		mbus_fmt->swapped		= true;
 		break;

 	case MEDIA_BUS_FMT_UYVY8_2X8:
-		sensor_fmt->fmt_order		= CEU_CAMCR_DTARY_8_UYVY;
-		sensor_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_VYUY;
-		sensor_fmt->swapped		= false;
+		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_UYVY;
+		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_VYUY;
+		mbus_fmt->swapped		= false;
 		break;

 	case MEDIA_BUS_FMT_VYUY8_2X8:
-		sensor_fmt->fmt_order		= CEU_CAMCR_DTARY_8_VYUY;
-		sensor_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_UYVY;
-		sensor_fmt->swapped		= true;
+		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_VYUY;
+		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_UYVY;
+		mbus_fmt->swapped		= true;
 		break;
 	}

@@ -1088,9 +1115,9 @@ static int ceu_init_formats(struct ceu_device *ceudev)
 static int ceu_runtime_suspend(struct device *dev)
 {
 	struct ceu_device *ceudev = dev_get_drvdata(dev);
-	struct v4l2_subdev *sensor = ceudev->sensor;
+	struct v4l2_subdev *v4l2_sd = ceudev->subdev->v4l2_subdev;

-	v4l2_subdev_call(sensor, core, s_power, 0);
+	v4l2_subdev_call(v4l2_sd, core, s_power, 0);

 	ceu_write(ceudev, CEU_CEIER, 0);
 	ceu_soft_reset(ceudev);
@@ -1104,9 +1131,9 @@ static int ceu_runtime_suspend(struct device *dev)
 static int ceu_runtime_resume(struct device *dev)
 {
 	struct ceu_device *ceudev = dev_get_drvdata(dev);
-	struct v4l2_subdev *sensor = ceudev->sensor;
+	struct v4l2_subdev *v4l2_sd = ceudev->subdev->v4l2_subdev;

-	v4l2_subdev_call(sensor, core, s_power, 1);
+	v4l2_subdev_call(v4l2_sd, core, s_power, 1);

 	ceu_soft_reset(ceudev);

@@ -1220,52 +1247,82 @@ static int ceu_enum_input(struct file *file, void *priv,

 static int ceu_g_input(struct file *file, void *priv, unsigned int *i)
 {
-	*i = 0;
+	struct ceu_device *ceudev = video_drvdata(file);
+
+	*i = ceudev->subdev_index;

 	return 0;
 }

 static int ceu_s_input(struct file *file, void *priv, unsigned int i)
 {
-	if (i > 0)
+	struct ceu_device *ceudev = video_drvdata(file);
+	struct ceu_subdev *ceu_sd_old;
+	int ret;
+
+	if (i >= ceudev->num_subdevs)
 		return -EINVAL;

+	ceu_sd_old	= ceudev->subdev;
+	ceudev->subdev	= &ceudev->subdevs[i];
+
+	/*
+	 * This is enough to make sure we can generate all available formats,
+	 * as long as we only support planar YUV422/YUV420 as memory output
+	 * formats.
+	 */
+	ret = ceu_init_formats(ceudev);
+	if (ret) {
+		ceudev->subdev = ceu_sd_old;
+		return -EINVAL;
+	}
+
+	/* now that we're sure we can use the sensor, power off the old one */
+	v4l2_subdev_call(ceu_sd_old->v4l2_subdev, core, s_power, 0);
+	v4l2_subdev_call(ceudev->subdev->v4l2_subdev, core, s_power, 1);
+
+	ceudev->subdev_index = i;
+
 	return 0;
 }

 static int ceu_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
 {
 	struct ceu_device *ceudev = video_drvdata(file);
+	struct v4l2_subdev *v4l2_subdev = ceudev->subdev->v4l2_subdev;

 	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
 		return -EINVAL;

-	return v4l2_subdev_call(ceudev->sensor, video, g_parm, a);
+	return v4l2_subdev_call(v4l2_subdev, video, g_parm, a);
 }

 static int ceu_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
 {
 	struct ceu_device *ceudev = video_drvdata(file);
+	struct v4l2_subdev *v4l2_subdev = ceudev->subdev->v4l2_subdev;

 	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
 		return -EINVAL;

-	return v4l2_subdev_call(ceudev->sensor, video, s_parm, a);
+	return v4l2_subdev_call(v4l2_subdev, video, s_parm, a);
 }

 static int ceu_enum_framesizes(struct file *file, void *fh,
 			       struct v4l2_frmsizeenum *fsize)
 {
 	struct ceu_device *ceudev = video_drvdata(file);
+	struct ceu_subdev *ceu_sd = ceudev->subdev;
+	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_subdev;
 	int ret;

 	struct v4l2_subdev_frame_size_enum fse = {
-		.code	= ceudev->sensor_fmt.mbus_code,
+		.code	= ceu_sd->mbus_fmt.mbus_code,
 		.index	= fsize->index,
 		.which	= V4L2_SUBDEV_FORMAT_ACTIVE,
 	};

-	ret = v4l2_subdev_call(ceudev->sensor, pad, enum_frame_size,
+	ret = v4l2_subdev_call(v4l2_sd, pad, enum_frame_size,
 			       NULL, &fse);
 	if (ret)
 		return ret;
@@ -1281,17 +1338,19 @@ static int ceu_enum_frameintervals(struct file *file, void *fh,
 				    struct v4l2_frmivalenum *fival)
 {
 	struct ceu_device *ceudev = video_drvdata(file);
+	struct ceu_subdev *ceu_sd = ceudev->subdev;
+	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_subdev;
 	int ret;

 	struct v4l2_subdev_frame_interval_enum fie = {
-		.code	= ceudev->sensor_fmt.mbus_code,
+		.code	= ceu_sd->mbus_fmt.mbus_code,
 		.index = fival->index,
 		.width = fival->width,
 		.height = fival->height,
 		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
 	};

-	ret = v4l2_subdev_call(ceudev->sensor, pad, enum_frame_interval, NULL,
+	ret = v4l2_subdev_call(v4l2_sd, pad, enum_frame_interval, NULL,
 			       &fie);
 	if (ret)
 		return ret;
@@ -1340,14 +1399,41 @@ void ceu_vdev_release(struct video_device *vdev)
 	kfree(ceudev);
 }

+/**
+ * ceu_sensor_bound() - Set v4l2_subdev and select primary sensor
+ */
 static int ceu_sensor_bound(struct v4l2_async_notifier *notifier,
-			    struct v4l2_subdev *subdev,
+			    struct v4l2_subdev *v4l2_sd,
 			    struct v4l2_async_subdev *asd)
 {
 	struct v4l2_device *v4l2_dev = notifier->v4l2_dev;
 	struct ceu_device *ceudev = v4l2_to_ceu(v4l2_dev);
+	struct ceu_subdev *ceu_sd = to_ceu_subdev(notifier);
+
+	if (video_is_registered(&ceudev->vdev)) {
+		v4l2_err(&ceudev->v4l2_dev, "only supports one sub-device.\n");
+		return -EBUSY;
+	}
+
+	if (ceu_sd->mbus_flags | CEU_FLAG_PRIMARY_SENS &&
+	    ceudev->subdev == NULL) {
+		ceudev->subdev_index = ceudev->num_subdevs;
+		ceudev->subdev = ceu_sd;
+	}
+
+	ceudev->num_subdevs++;
+	ceu_sd->v4l2_subdev = v4l2_sd;
+
+	return 0;
+}
+
+static int ceu_sensor_complete(struct v4l2_async_notifier *notifier)
+{
+	struct v4l2_device *v4l2_dev = notifier->v4l2_dev;
+	struct ceu_device *ceudev = v4l2_to_ceu(v4l2_dev);
 	struct video_device *vdev = &ceudev->vdev;
 	struct vb2_queue *q = &ceudev->vb2_vq;
+	struct v4l2_subdev *v4l2_sd;
 	int ret;

 	/* Initialize vb2 queue */
@@ -1365,8 +1451,15 @@ static int ceu_sensor_bound(struct v4l2_async_notifier *notifier,
 	if (ret)
 		return ret;

-	/* Initialize formats and set default format on sensor */
-	ceudev->sensor = subdev;
+	/*
+	 * Make sure at least one sensor is primary and use it to initialize
+	 * ceu formats
+	 */
+	if (ceudev->subdev == NULL)
+		ceudev->subdev = list_first_entry(&ceudev->subdev_list,
+						  struct ceu_subdev, list);
+	v4l2_sd = ceudev->subdev->v4l2_subdev;
+
 	ret = ceu_init_formats(ceudev);
 	if (ret)
 		return ret;
@@ -1380,7 +1473,7 @@ static int ceu_sensor_bound(struct v4l2_async_notifier *notifier,
 	vdev->v4l2_dev		= v4l2_dev;
 	vdev->lock		= &ceudev->mlock;
 	vdev->queue		= &ceudev->vb2_vq;
-	vdev->ctrl_handler	= subdev->ctrl_handler;
+	vdev->ctrl_handler	= v4l2_sd->ctrl_handler;
 	vdev->fops		= &ceu_fops;
 	vdev->ioctl_ops		= &ceu_ioctl_ops;
 	vdev->release		= ceu_vdev_release;
@@ -1394,12 +1487,56 @@ static int ceu_sensor_bound(struct v4l2_async_notifier *notifier,
 		return ret;
 	}

+	dev_info(ceudev->dev, "Video device registered\n");
+
+	return 0;
+}
+
+/**
+ * Parse platform data to collect bus flags and async sensors description
+ */
+static int ceu_parse_platform_data(struct ceu_device *ceudev, void *pdata)
+{
+	struct ceu_async_subdev *async_sd;
+	struct ceu_info *info = pdata;
+	struct ceu_subdev *ceu_sd;
+	unsigned int i;
+
+	ceudev->pdata	= pdata;
+	ceudev->subdev	= NULL;
+	ceudev->subdev_index = 0;
+	ceudev->num_subdevs = 0;
+	ceudev->num_subdevs = info->num_subdevs;
+	ceudev->subdevs = devm_kcalloc(ceudev->dev, info->num_subdevs,
+				       sizeof(*ceudev->subdevs),
+				       GFP_KERNEL);
+	if (!ceudev->subdevs)
+		return -ENOMEM;
+
+	for (i = 0; i < info->num_subdevs; i++) {
+		ceu_sd = &ceudev->subdevs[i];
+		async_sd = &info->subdevs[i];
+
+		ceu_sd->asd = devm_kmalloc(ceudev->dev, sizeof(*ceu_sd->asd),
+					   GFP_KERNEL);
+		if (!ceu_sd)
+			return -ENOMEM;
+
+		ceu_sd->mbus_flags	= async_sd->flags;
+		ceu_sd->asd->match_type	= V4L2_ASYNC_MATCH_I2C;
+		ceu_sd->asd->match.i2c.adapter_id = async_sd->i2c_adapter_id;
+		ceu_sd->asd->match.i2c.address = async_sd->i2c_address;
+
+		list_add_tail(&ceu_sd->list, &ceudev->subdev_list);
+	}
+
 	return 0;
 }

 static int ceu_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
+	struct ceu_subdev *ceu_sd;
 	struct ceu_device *ceudev;
 	struct resource *res;
 	void __iomem *base;
@@ -1415,6 +1552,7 @@ static int ceu_probe(struct platform_device *pdev)
 	ceudev->dev = dev;

 	INIT_LIST_HEAD(&ceudev->capture);
+	INIT_LIST_HEAD(&ceudev->subdev_list);
 	spin_lock_init(&ceudev->lock);
 	mutex_init(&ceudev->mlock);

@@ -1440,13 +1578,12 @@ static int ceu_probe(struct platform_device *pdev)
 	if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
 		/* TODO: implement OF parsing */
 	} else if (dev->platform_data) {
-		ceudev->pdata = dev->platform_data;
-		ceudev->mbus_flags = ceudev->pdata->flags;
-		ceudev->asd.match_type = V4L2_ASYNC_MATCH_I2C;
-		ceudev->asd.match.i2c.adapter_id =
-			ceudev->pdata->sensor_i2c_adapter_id;
-		ceudev->asd.match.i2c.address =
-			ceudev->pdata->sensor_i2c_address;
+		ret = ceu_parse_platform_data(ceudev, dev->platform_data);
+		if (ret) {
+			dev_err(&pdev->dev,
+				"CEU unable to parse platform data\n");
+			return ret;
+		}
 	} else {
 		dev_err(&pdev->dev, "CEU platform data not set.\n");
 		return -EINVAL;
@@ -1459,20 +1596,27 @@ static int ceu_probe(struct platform_device *pdev)
 	if (ret)
 		goto error_pm_disable;

-	ceudev->asds[0]			= &ceudev->asd;
-	ceudev->notifier.v4l2_dev	= &ceudev->v4l2_dev;
-	ceudev->notifier.subdevs	= ceudev->asds;
-	ceudev->notifier.num_subdevs	= 1;
-	ceudev->notifier.bound		= ceu_sensor_bound;
-	ret = v4l2_async_notifier_register(&ceudev->v4l2_dev,
-					   &ceudev->notifier);
-	if (ret)
-		goto error_v4l2_unregister;
+	list_for_each_entry(ceu_sd, &ceudev->subdev_list, list) {
+		ceu_sd->notifier.v4l2_dev	= &ceudev->v4l2_dev;
+		ceu_sd->notifier.subdevs	= &ceu_sd->asd;
+		ceu_sd->notifier.num_subdevs	= 1;
+		ceu_sd->notifier.bound		= ceu_sensor_bound;
+		ceu_sd->notifier.complete	= ceu_sensor_complete;
+		ret = v4l2_async_notifier_register(&ceudev->v4l2_dev,
+						   &ceu_sd->notifier);
+		if (ret)
+			goto error_v4l2_unregister_notifiers;
+
+	}

 	return 0;

-error_v4l2_unregister:
+error_v4l2_unregister_notifiers:
+	list_for_each_entry(ceu_sd, &ceudev->subdev_list, list)
+		v4l2_async_unregister_subdev(ceu_sd->v4l2_subdev);
+
 	v4l2_device_unregister(&ceudev->v4l2_dev);
+
 error_pm_disable:
 	pm_runtime_disable(dev);

@@ -1482,10 +1626,13 @@ static int ceu_probe(struct platform_device *pdev)
 static int ceu_remove(struct platform_device *pdev)
 {
 	struct ceu_device *ceudev = platform_get_drvdata(pdev);
+	struct ceu_subdev *ceu_sd;

 	pm_runtime_disable(ceudev->dev);

-	v4l2_async_notifier_unregister(&ceudev->notifier);
+	list_for_each_entry(ceu_sd, &ceudev->subdev_list, list)
+		v4l2_async_unregister_subdev(ceu_sd->v4l2_subdev);
+
 	v4l2_device_unregister(&ceudev->v4l2_dev);

 	video_unregister_device(&ceudev->vdev);
--
2.7.4

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC v4 5/8] media: i2c: ov772x: Force use of SCCB protocol
  2017-05-19 16:02 [RFC v4 0/8] Renesas CEU driver Jacopo Mondi
                   ` (3 preceding siblings ...)
  2017-05-19 16:02 ` [RFC v4 4/8] media: platform: ceu: Support for multiple subdevs Jacopo Mondi
@ 2017-05-19 16:02 ` Jacopo Mondi
  2017-05-19 16:02 ` [RFC v4 6/8] media: i2c: ov772x: Remove soc_camera dependencies Jacopo Mondi
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 10+ messages in thread
From: Jacopo Mondi @ 2017-05-19 16:02 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm; +Cc: linux-renesas-soc

Commit e78902976150 ("i2c: sh_mobile: don't send a stop condition by
default inside transfers") makes the i2c_sh_mobile I2C-adapter emit a
stop/start sequence between messages in a single transfer only when
explicitly requested with I2C_M_STOP.

This breaks the ov772x driver in the SH4 Migo-R board as the Omnivision
sensor uses the I2C-like SCCB protocol that doesn't support repeated
starts:

i2c-sh_mobile i2c-sh_mobile.0: Transfer request timed out
ov772x 0-0021: Product ID error 92:92

Fix it by marking the client as SCCB, forcing the emission of a
stop/start sequence between all messages.
As I2C_M_STOP requires the I2C adapter to support protocol mangling,
ensure that the I2C_FUNC_PROTOCOL_MANGLING functionality is available.

Tested on SH4 Migo-R board, with OV772x now successfully probing

soc-camera-pdrv soc-camera-pdrv.0: Probing soc-camera-pdrv.0
ov772x 0-0021: ov7725 Product ID 77:21 Manufacturer ID 7f:a2

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Acked-by: Wolfram Sang <wsa+renesas@sang-engineering.com>
Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 drivers/media/i2c/soc_camera/ov772x.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/drivers/media/i2c/soc_camera/ov772x.c b/drivers/media/i2c/soc_camera/ov772x.c
index 985a367..351abec 100644
--- a/drivers/media/i2c/soc_camera/ov772x.c
+++ b/drivers/media/i2c/soc_camera/ov772x.c
@@ -1062,11 +1062,13 @@ static int ov772x_probe(struct i2c_client *client,
 		return -EINVAL;
 	}
 
-	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
+					      I2C_FUNC_PROTOCOL_MANGLING)) {
 		dev_err(&adapter->dev,
-			"I2C-Adapter doesn't support I2C_FUNC_SMBUS_BYTE_DATA\n");
+			"I2C-Adapter doesn't support SMBUS_BYTE_DATA or PROTOCOL_MANGLING\n");
 		return -EIO;
 	}
+	client->flags |= I2C_CLIENT_SCCB;
 
 	priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
 	if (!priv)
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC v4 6/8] media: i2c: ov772x: Remove soc_camera dependencies
  2017-05-19 16:02 [RFC v4 0/8] Renesas CEU driver Jacopo Mondi
                   ` (4 preceding siblings ...)
  2017-05-19 16:02 ` [RFC v4 5/8] media: i2c: ov772x: Force use of SCCB protocol Jacopo Mondi
@ 2017-05-19 16:02 ` Jacopo Mondi
  2017-05-19 16:02 ` [RFC v4 7/8] media: i2c: tw9910: " Jacopo Mondi
  2017-05-19 16:03 ` [RFC v4 8/8] arch: sh: migor: Use new CEU camera driver Jacopo Mondi
  7 siblings, 0 replies; 10+ messages in thread
From: Jacopo Mondi @ 2017-05-19 16:02 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm; +Cc: linux-renesas-soc

Remove soc_camera framework dependencies from OV772x sensor driver.
TODO: move the driver out from soc_camera directory

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
---
 drivers/media/i2c/soc_camera/ov772x.c | 90 ++++++++++++++++++++++++-----------
 include/media/i2c/ov772x.h            |  3 ++
 2 files changed, 65 insertions(+), 28 deletions(-)

diff --git a/drivers/media/i2c/soc_camera/ov772x.c b/drivers/media/i2c/soc_camera/ov772x.c
index 351abec..c5596b3 100644
--- a/drivers/media/i2c/soc_camera/ov772x.c
+++ b/drivers/media/i2c/soc_camera/ov772x.c
@@ -15,6 +15,7 @@
  * published by the Free Software Foundation.
  */
 
+#include <linux/clk.h>
 #include <linux/init.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
@@ -25,8 +26,8 @@
 #include <linux/videodev2.h>
 
 #include <media/i2c/ov772x.h>
-#include <media/soc_camera.h>
-#include <media/v4l2-clk.h>
+
+#include <media/v4l2-device.h>
 #include <media/v4l2-ctrls.h>
 #include <media/v4l2-subdev.h>
 #include <media/v4l2-image-sizes.h>
@@ -393,7 +394,7 @@ struct ov772x_win_size {
 struct ov772x_priv {
 	struct v4l2_subdev                subdev;
 	struct v4l2_ctrl_handler	  hdl;
-	struct v4l2_clk			 *clk;
+	struct clk			 *clk;
 	struct ov772x_camera_info        *info;
 	const struct ov772x_color_format *cfmt;
 	const struct ov772x_win_size     *win;
@@ -550,7 +551,7 @@ static int ov772x_reset(struct i2c_client *client)
 }
 
 /*
- * soc_camera_ops function
+ * subdev ops
  */
 
 static int ov772x_s_stream(struct v4l2_subdev *sd, int enable)
@@ -650,13 +651,36 @@ static int ov772x_s_register(struct v4l2_subdev *sd,
 }
 #endif
 
+static int ov772x_power_on(struct ov772x_priv *priv)
+{
+	int ret;
+
+	if (priv->info->platform_enable) {
+		ret = priv->info->platform_enable();
+		if (ret)
+			return ret;
+	}
+
+	/*  drivers/sh/clk/core.c returns -EINVAL if clk is NULL */
+	return clk_enable(priv->clk) <= 0 ? 0 : 1;
+}
+
+static int ov772x_power_off(struct ov772x_priv *priv)
+{
+	if (priv->info->platform_enable)
+		priv->info->platform_disable();
+
+	clk_disable(priv->clk);
+
+	return 0;
+}
+
 static int ov772x_s_power(struct v4l2_subdev *sd, int on)
 {
-	struct i2c_client *client = v4l2_get_subdevdata(sd);
-	struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
 	struct ov772x_priv *priv = to_ov772x(sd);
 
-	return soc_camera_set_power(&client->dev, ssdd, priv->clk, on);
+	return on ? ov772x_power_on(priv) :
+		    ov772x_power_off(priv);
 }
 
 static const struct ov772x_win_size *ov772x_select_win(u32 width, u32 height)
@@ -975,6 +999,7 @@ static int ov772x_video_probe(struct ov772x_priv *priv)
 		goto done;
 	}
 
+
 	dev_info(&client->dev,
 		 "%s Product ID %0x:%0x Manufacturer ID %x:%x\n",
 		 devname,
@@ -1015,14 +1040,10 @@ static int ov772x_enum_mbus_code(struct v4l2_subdev *sd,
 static int ov772x_g_mbus_config(struct v4l2_subdev *sd,
 				struct v4l2_mbus_config *cfg)
 {
-	struct i2c_client *client = v4l2_get_subdevdata(sd);
-	struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
-
 	cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_MASTER |
 		V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_HIGH |
 		V4L2_MBUS_DATA_ACTIVE_HIGH;
 	cfg->type = V4L2_MBUS_PARALLEL;
-	cfg->flags = soc_camera_apply_board_flags(ssdd, cfg);
 
 	return 0;
 }
@@ -1053,12 +1074,11 @@ static int ov772x_probe(struct i2c_client *client,
 			const struct i2c_device_id *did)
 {
 	struct ov772x_priv	*priv;
-	struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
-	struct i2c_adapter	*adapter = to_i2c_adapter(client->dev.parent);
+	struct i2c_adapter	*adapter = client->adapter;
 	int			ret;
 
-	if (!ssdd || !ssdd->drv_priv) {
-		dev_err(&client->dev, "OV772X: missing platform data!\n");
+	if (!client->dev.platform_data) {
+		dev_err(&adapter->dev, "Missing OV7725 platform data\n");
 		return -EINVAL;
 	}
 
@@ -1074,7 +1094,7 @@ static int ov772x_probe(struct i2c_client *client,
 	if (!priv)
 		return -ENOMEM;
 
-	priv->info = ssdd->drv_priv;
+	priv->info = client->dev.platform_data;
 
 	v4l2_i2c_subdev_init(&priv->subdev, client, &ov772x_subdev_ops);
 	v4l2_ctrl_handler_init(&priv->hdl, 3);
@@ -1088,21 +1108,34 @@ static int ov772x_probe(struct i2c_client *client,
 	if (priv->hdl.error)
 		return priv->hdl.error;
 
-	priv->clk = v4l2_clk_get(&client->dev, "mclk");
-	if (IS_ERR(priv->clk)) {
+	priv->clk = clk_get(&client->dev, "mclk");
+	if (PTR_ERR(priv->clk) == -ENOENT) {
+		priv->clk = NULL;
+	} else if (IS_ERR(priv->clk)) {
+		dev_err(&client->dev, "Unable to get mclk clock\n");
 		ret = PTR_ERR(priv->clk);
-		goto eclkget;
+		goto error_clk_enable;
 	}
 
 	ret = ov772x_video_probe(priv);
-	if (ret < 0) {
-		v4l2_clk_put(priv->clk);
-eclkget:
-		v4l2_ctrl_handler_free(&priv->hdl);
-	} else {
-		priv->cfmt = &ov772x_cfmts[0];
-		priv->win = &ov772x_win_sizes[0];
-	}
+	if (ret < 0)
+		goto error_video_probe;
+
+
+	priv->cfmt = &ov772x_cfmts[0];
+	priv->win = &ov772x_win_sizes[0];
+
+	ret = v4l2_async_register_subdev(&priv->subdev);
+	if (ret)
+		goto error_video_probe;
+
+	return 0;
+
+error_video_probe:
+	if (priv->clk)
+		clk_put(priv->clk);
+error_clk_enable:
+	v4l2_ctrl_handler_free(&priv->hdl);
 
 	return ret;
 }
@@ -1111,7 +1144,8 @@ static int ov772x_remove(struct i2c_client *client)
 {
 	struct ov772x_priv *priv = to_ov772x(i2c_get_clientdata(client));
 
-	v4l2_clk_put(priv->clk);
+	if (priv->clk)
+		clk_put(priv->clk);
 	v4l2_device_unregister_subdev(&priv->subdev);
 	v4l2_ctrl_handler_free(&priv->hdl);
 	return 0;
diff --git a/include/media/i2c/ov772x.h b/include/media/i2c/ov772x.h
index 00dbb7c..5896dff 100644
--- a/include/media/i2c/ov772x.h
+++ b/include/media/i2c/ov772x.h
@@ -54,6 +54,9 @@ struct ov772x_edge_ctrl {
 struct ov772x_camera_info {
 	unsigned long		flags;
 	struct ov772x_edge_ctrl	edgectrl;
+
+	int (*platform_enable)(void);
+	void (*platform_disable)(void);
 };
 
 #endif /* __OV772X_H__ */
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC v4 7/8] media: i2c: tw9910: Remove soc_camera dependencies
  2017-05-19 16:02 [RFC v4 0/8] Renesas CEU driver Jacopo Mondi
                   ` (5 preceding siblings ...)
  2017-05-19 16:02 ` [RFC v4 6/8] media: i2c: ov772x: Remove soc_camera dependencies Jacopo Mondi
@ 2017-05-19 16:02 ` Jacopo Mondi
  2017-05-19 16:03 ` [RFC v4 8/8] arch: sh: migor: Use new CEU camera driver Jacopo Mondi
  7 siblings, 0 replies; 10+ messages in thread
From: Jacopo Mondi @ 2017-05-19 16:02 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm; +Cc: linux-renesas-soc

Remove soc_camera framework dependencies from TW9910 driver.
TODO: move the driver out from soc_camera directory

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
---
 drivers/media/i2c/soc_camera/tw9910.c | 68 +++++++++++++++++++++++------------
 include/media/i2c/tw9910.h            |  6 ++++
 2 files changed, 52 insertions(+), 22 deletions(-)

diff --git a/drivers/media/i2c/soc_camera/tw9910.c b/drivers/media/i2c/soc_camera/tw9910.c
index c9c49ed..6809939 100644
--- a/drivers/media/i2c/soc_camera/tw9910.c
+++ b/drivers/media/i2c/soc_camera/tw9910.c
@@ -16,6 +16,7 @@
  * published by the Free Software Foundation.
  */

+#include <linux/clk.h>
 #include <linux/init.h>
 #include <linux/module.h>
 #include <linux/i2c.h>
@@ -25,9 +26,7 @@
 #include <linux/v4l2-mediabus.h>
 #include <linux/videodev2.h>

-#include <media/soc_camera.h>
 #include <media/i2c/tw9910.h>
-#include <media/v4l2-clk.h>
 #include <media/v4l2-subdev.h>

 #define GET_ID(val)  ((val & 0xF8) >> 3)
@@ -228,7 +227,7 @@ struct tw9910_scale_ctrl {

 struct tw9910_priv {
 	struct v4l2_subdev		subdev;
-	struct v4l2_clk			*clk;
+	struct clk			*clk;
 	struct tw9910_video_info	*info;
 	const struct tw9910_scale_ctrl	*scale;
 	v4l2_std_id			norm;
@@ -582,13 +581,37 @@ static int tw9910_s_register(struct v4l2_subdev *sd,
 }
 #endif

+static int tw9910_power_on(struct tw9910_priv *priv)
+{
+	int ret;
+
+	if (priv->info->platform_enable) {
+		ret = priv->info->platform_enable();
+		if (ret)
+			return ret;
+	}
+
+       /*  drivers/sh/clk/core.c returns -EINVAL if clk is NULL */
+	return clk_enable(priv->clk) <= 0 ? 0 : 1;
+}
+
+static int tw9910_power_off(struct tw9910_priv *priv)
+{
+	if (priv->info->platform_enable)
+		priv->info->platform_disable();
+
+	clk_disable(priv->clk);
+
+	return 0;
+}
+
 static int tw9910_s_power(struct v4l2_subdev *sd, int on)
 {
 	struct i2c_client *client = v4l2_get_subdevdata(sd);
-	struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
 	struct tw9910_priv *priv = to_tw9910(client);

-	return soc_camera_set_power(&client->dev, ssdd, priv->clk, on);
+	return on ? tw9910_power_on(priv) :
+		    tw9910_power_off(priv);
 }

 static int tw9910_set_frame(struct v4l2_subdev *sd, u32 *width, u32 *height)
@@ -614,7 +637,7 @@ static int tw9910_set_frame(struct v4l2_subdev *sd, u32 *width, u32 *height)
 	 * set bus width
 	 */
 	val = 0x00;
-	if (SOCAM_DATAWIDTH_16 == priv->info->buswidth)
+	if (priv->info->buswidth == TW9910_DATAWIDTH_16)
 		val = LEN;

 	ret = tw9910_mask_set(client, OPFORM, LEN, val);
@@ -799,8 +822,8 @@ static int tw9910_video_probe(struct i2c_client *client)
 	/*
 	 * tw9910 only use 8 or 16 bit bus width
 	 */
-	if (SOCAM_DATAWIDTH_16 != priv->info->buswidth &&
-	    SOCAM_DATAWIDTH_8  != priv->info->buswidth) {
+	if (priv->info->buswidth != TW9910_DATAWIDTH_16 &&
+	    priv->info->buswidth != TW9910_DATAWIDTH_8) {
 		dev_err(&client->dev, "bus width error\n");
 		return -ENODEV;
 	}
@@ -859,15 +882,11 @@ static int tw9910_enum_mbus_code(struct v4l2_subdev *sd,
 static int tw9910_g_mbus_config(struct v4l2_subdev *sd,
 				struct v4l2_mbus_config *cfg)
 {
-	struct i2c_client *client = v4l2_get_subdevdata(sd);
-	struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
-
 	cfg->flags = V4L2_MBUS_PCLK_SAMPLE_RISING | V4L2_MBUS_MASTER |
 		V4L2_MBUS_VSYNC_ACTIVE_HIGH | V4L2_MBUS_VSYNC_ACTIVE_LOW |
 		V4L2_MBUS_HSYNC_ACTIVE_HIGH | V4L2_MBUS_HSYNC_ACTIVE_LOW |
 		V4L2_MBUS_DATA_ACTIVE_HIGH;
 	cfg->type = V4L2_MBUS_PARALLEL;
-	cfg->flags = soc_camera_apply_board_flags(ssdd, cfg);

 	return 0;
 }
@@ -876,9 +895,8 @@ static int tw9910_s_mbus_config(struct v4l2_subdev *sd,
 				const struct v4l2_mbus_config *cfg)
 {
 	struct i2c_client *client = v4l2_get_subdevdata(sd);
-	struct soc_camera_subdev_desc *ssdd = soc_camera_i2c_to_desc(client);
 	u8 val = VSSL_VVALID | HSSL_DVALID;
-	unsigned long flags = soc_camera_apply_board_flags(ssdd, cfg);
+	unsigned long flags = cfg->flags;

 	/*
 	 * set OUTCTR1
@@ -935,15 +953,14 @@ static int tw9910_probe(struct i2c_client *client,
 	struct tw9910_video_info	*info;
 	struct i2c_adapter		*adapter =
 		to_i2c_adapter(client->dev.parent);
-	struct soc_camera_subdev_desc	*ssdd = soc_camera_i2c_to_desc(client);
 	int ret;

-	if (!ssdd || !ssdd->drv_priv) {
+	if (!client->dev.platform_data) {
 		dev_err(&client->dev, "TW9910: missing platform data!\n");
 		return -EINVAL;
 	}

-	info = ssdd->drv_priv;
+	info = client->dev.platform_data;

 	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
 		dev_err(&client->dev,
@@ -959,13 +976,17 @@ static int tw9910_probe(struct i2c_client *client,

 	v4l2_i2c_subdev_init(&priv->subdev, client, &tw9910_subdev_ops);

-	priv->clk = v4l2_clk_get(&client->dev, "mclk");
-	if (IS_ERR(priv->clk))
+	priv->clk = clk_get(&client->dev, "mclk");
+	if (PTR_ERR(priv->clk) == -ENOENT) {
+		priv->clk = NULL;
+	} else if (IS_ERR(priv->clk)) {
+		dev_err(&client->dev, "Unable to get mclk clock\n");
 		return PTR_ERR(priv->clk);
+	}

 	ret = tw9910_video_probe(client);
-	if (ret < 0)
-		v4l2_clk_put(priv->clk);
+	if (ret < 0 && priv->clk)
+		clk_put(priv->clk);

 	return ret;
 }
@@ -973,7 +994,10 @@ static int tw9910_probe(struct i2c_client *client,
 static int tw9910_remove(struct i2c_client *client)
 {
 	struct tw9910_priv *priv = to_tw9910(client);
-	v4l2_clk_put(priv->clk);
+
+	if (priv->clk)
+		clk_put(priv->clk);
+
 	return 0;
 }

diff --git a/include/media/i2c/tw9910.h b/include/media/i2c/tw9910.h
index 90bcf1f..b80e45c 100644
--- a/include/media/i2c/tw9910.h
+++ b/include/media/i2c/tw9910.h
@@ -18,6 +18,9 @@

 #include <media/soc_camera.h>

+#define TW9910_DATAWIDTH_8	BIT(0)
+#define TW9910_DATAWIDTH_16	BIT(1)
+
 enum tw9910_mpout_pin {
 	TW9910_MPO_VLOSS,
 	TW9910_MPO_HLOCK,
@@ -32,6 +35,9 @@ enum tw9910_mpout_pin {
 struct tw9910_video_info {
 	unsigned long		buswidth;
 	enum tw9910_mpout_pin	mpout;
+
+	int (*platform_enable)(void);
+	void (*platform_disable)(void);
 };


--
2.7.4

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC v4 8/8] arch: sh: migor: Use new CEU camera driver
  2017-05-19 16:02 [RFC v4 0/8] Renesas CEU driver Jacopo Mondi
                   ` (6 preceding siblings ...)
  2017-05-19 16:02 ` [RFC v4 7/8] media: i2c: tw9910: " Jacopo Mondi
@ 2017-05-19 16:03 ` Jacopo Mondi
  7 siblings, 0 replies; 10+ messages in thread
From: Jacopo Mondi @ 2017-05-19 16:03 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm; +Cc: linux-renesas-soc

Update the interface for CEU camera driver and update migor machine init
to use it.
Get rid of soc_camera_links and and register subdevices as I2c devices.
Change power on/off routines to be called by sensor driver to
select/deselect which sensor to use.

Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
---
 arch/sh/boards/mach-migor/setup.c | 125 +++++++++++++++-----------------------
 1 file changed, 49 insertions(+), 76 deletions(-)

diff --git a/arch/sh/boards/mach-migor/setup.c b/arch/sh/boards/mach-migor/setup.c
index 5de60a7..6b67ed5 100644
--- a/arch/sh/boards/mach-migor/setup.c
+++ b/arch/sh/boards/mach-migor/setup.c
@@ -26,7 +26,7 @@
 #include <linux/videodev2.h>
 #include <linux/sh_intc.h>
 #include <video/sh_mobile_lcdc.h>
-#include <media/drv-intf/sh_mobile_ceu.h>
+#include <media/drv-intf/renesas_ceu.h>
 #include <media/i2c/ov772x.h>
 #include <media/soc_camera.h>
 #include <media/i2c/tw9910.h>
@@ -302,12 +302,9 @@ static struct platform_device migor_lcdc_device = {
 };
 
 static struct clk *camera_clk;
-static DEFINE_MUTEX(camera_lock);
 
-static void camera_power_on(int is_tw)
+static void camera_vio_clk_on(void)
 {
-	mutex_lock(&camera_lock);
-
 	/* Use 10 MHz VIO_CKO instead of 24 MHz to work
 	 * around signal quality issues on Panel Board V2.1.
 	 */
@@ -317,49 +314,52 @@ static void camera_power_on(int is_tw)
 
 	/* use VIO_RST to take camera out of reset */
 	mdelay(10);
-	if (is_tw) {
-		gpio_set_value(GPIO_PTT2, 0);
-		gpio_set_value(GPIO_PTT0, 0);
-	} else {
-		gpio_set_value(GPIO_PTT0, 1);
-	}
 	gpio_set_value(GPIO_PTT3, 0);
 	mdelay(10);
 	gpio_set_value(GPIO_PTT3, 1);
 	mdelay(10); /* wait to let chip come out of reset */
 }
 
-static void camera_power_off(void)
+/* Platform camera driver to make sure these two are enabled alternatively */
+static int ov7725_enable(void)
 {
-	clk_disable(camera_clk); /* stop VIO_CKO */
-	clk_put(camera_clk);
+	gpio_set_value(GPIO_PTT0, 1);
 
-	gpio_set_value(GPIO_PTT3, 0);
-	mutex_unlock(&camera_lock);
+	return 0;
 }
 
-static int ov7725_power(struct device *dev, int mode)
+static void ov7725_disable(void)
 {
-	if (mode)
-		camera_power_on(0);
-	else
-		camera_power_off();
-
-	return 0;
+	gpio_set_value(GPIO_PTT0, 0);
 }
 
-static int tw9910_power(struct device *dev, int mode)
+static int tw9910_enable(void)
 {
-	if (mode)
-		camera_power_on(1);
-	else
-		camera_power_off();
+	gpio_set_value(GPIO_PTT2, 1);
 
 	return 0;
 }
 
-static struct sh_mobile_ceu_info sh_mobile_ceu_info = {
-	.flags = SH_CEU_FLAG_USE_8BIT_BUS,
+static void tw9910_disable(void)
+{
+	gpio_set_value(GPIO_PTT2, 0);
+}
+
+static struct ceu_info ceu_info = {
+	.num_subdevs		= 2,
+	.subdevs = {
+		{ /* [0] = ov772x */
+			.flags		= CEU_FLAG_USE_8BIT_BUS |
+					  CEU_FLAG_PRIMARY_SENS,
+			.i2c_adapter_id	= 0,
+			.i2c_address	= 0x21,
+		},
+		{ /* [1] = tw9910 */
+			.flags		= CEU_FLAG_USE_8BIT_BUS,
+			.i2c_adapter_id	= 0,
+			.i2c_address	= 0x45,
+		},
+	},
 };
 
 static struct resource migor_ceu_resources[] = {
@@ -379,12 +379,12 @@ static struct resource migor_ceu_resources[] = {
 };
 
 static struct platform_device migor_ceu_device = {
-	.name		= "sh_mobile_ceu",
+	.name		= "renesas-ceu",
 	.id             = 0, /* "ceu0" clock */
 	.num_resources	= ARRAY_SIZE(migor_ceu_resources),
 	.resource	= migor_ceu_resources,
 	.dev	= {
-		.platform_data	= &sh_mobile_ceu_info,
+		.platform_data	= &ceu_info,
 	},
 };
 
@@ -423,6 +423,20 @@ static struct platform_device sdhi_cn9_device = {
 	},
 };
 
+static struct ov772x_camera_info ov7725_info = {
+	.platform_enable = ov7725_enable,
+	.platform_disable = ov7725_disable,
+};
+
+static struct tw9910_video_info tw9910_info = {
+	.buswidth       = TW9910_DATAWIDTH_8,
+	.mpout          = TW9910_MPO_FIELD,
+
+	.platform_enable = tw9910_enable,
+	.platform_disable = tw9910_disable,
+};
+
+
 static struct i2c_board_info migor_i2c_devices[] = {
 	{
 		I2C_BOARD_INFO("rs5c372b", 0x32),
@@ -434,51 +448,13 @@ static struct i2c_board_info migor_i2c_devices[] = {
 	{
 		I2C_BOARD_INFO("wm8978", 0x1a),
 	},
-};
-
-static struct i2c_board_info migor_i2c_camera[] = {
 	{
 		I2C_BOARD_INFO("ov772x", 0x21),
+		.platform_data = &ov7725_info,
 	},
 	{
 		I2C_BOARD_INFO("tw9910", 0x45),
-	},
-};
-
-static struct ov772x_camera_info ov7725_info;
-
-static struct soc_camera_link ov7725_link = {
-	.power		= ov7725_power,
-	.board_info	= &migor_i2c_camera[0],
-	.i2c_adapter_id	= 0,
-	.priv		= &ov7725_info,
-};
-
-static struct tw9910_video_info tw9910_info = {
-	.buswidth	= SOCAM_DATAWIDTH_8,
-	.mpout		= TW9910_MPO_FIELD,
-};
-
-static struct soc_camera_link tw9910_link = {
-	.power		= tw9910_power,
-	.board_info	= &migor_i2c_camera[1],
-	.i2c_adapter_id	= 0,
-	.priv		= &tw9910_info,
-};
-
-static struct platform_device migor_camera[] = {
-	{
-		.name	= "soc-camera-pdrv",
-		.id	= 0,
-		.dev	= {
-			.platform_data = &ov7725_link,
-		},
-	}, {
-		.name	= "soc-camera-pdrv",
-		.id	= 1,
-		.dev	= {
-			.platform_data = &tw9910_link,
-		},
+		.platform_data = &tw9910_info,
 	},
 };
 
@@ -490,8 +466,6 @@ static struct platform_device *migor_devices[] __initdata = {
 	&migor_nor_flash_device,
 	&migor_nand_flash_device,
 	&sdhi_cn9_device,
-	&migor_camera[0],
-	&migor_camera[1],
 };
 
 extern char migor_sdram_enter_start;
@@ -632,8 +606,6 @@ static int __init migor_devices_setup(void)
 #endif
 	__raw_writew(__raw_readw(PORT_MSELCRB) | 0x2000, PORT_MSELCRB); /* D15->D8 */
 
-	platform_resource_setup_memory(&migor_ceu_device, "ceu", 4 << 20);
-
 	/* SIU: Port B */
 	gpio_request(GPIO_FN_SIUBOLR, NULL);
 	gpio_request(GPIO_FN_SIUBOBT, NULL);
@@ -647,6 +619,7 @@ static int __init migor_devices_setup(void)
 	 */
 	__raw_writew(__raw_readw(PORT_MSELCRA) | 1, PORT_MSELCRA);
 
+	camera_vio_clk_on();
 	i2c_register_board_info(0, migor_i2c_devices,
 				ARRAY_SIZE(migor_i2c_devices));
 
-- 
2.7.4

^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [RFC v4 1/8] include: media: Move and update CEU driver interface
  2017-05-19 16:02 ` [RFC v4 1/8] include: media: Move and update CEU driver interface Jacopo Mondi
@ 2017-05-20 11:59   ` Laurent Pinchart
  0 siblings, 0 replies; 10+ messages in thread
From: Laurent Pinchart @ 2017-05-20 11:59 UTC (permalink / raw)
  To: Jacopo Mondi; +Cc: magnus.damm, linux-renesas-soc

Hi Jacopo,

Thank you for the patch.

On Friday 19 May 2017 18:02:53 Jacopo Mondi wrote:
> Update Renesas CEU driver interface to use driver interface

I'm not sure this matches the content of the patch.

> SQUASH

? :-)

> Signed-off-by: Jacopo Mondi <jacopo@jmondi.org>
> ---
>  include/media/drv-intf/renesas_ceu.h   | 28 ++++++++++++++++++++++++++++
>  include/media/drv-intf/sh_mobile_ceu.h | 28 ----------------------------
>  2 files changed, 28 insertions(+), 28 deletions(-)
>  create mode 100644 include/media/drv-intf/renesas_ceu.h
>  delete mode 100644 include/media/drv-intf/sh_mobile_ceu.h
> 
> diff --git a/include/media/drv-intf/renesas_ceu.h
> b/include/media/drv-intf/renesas_ceu.h new file mode 100644
> index 0000000..291661d
> --- /dev/null
> +++ b/include/media/drv-intf/renesas_ceu.h
> @@ -0,0 +1,28 @@
> +#ifndef __ASM_RENESAS_CEU_H__
> +#define __ASM_RENESAS_CEU_H__
> +
> +#define CEU_FLAG_USE_8BIT_BUS   (1 << 0) /* use  8bit bus width */
> +#define CEU_FLAG_USE_16BIT_BUS  (1 << 1) /* use 16bit bus width */
> +#define CEU_FLAG_HSYNC_LOW      (1 << 2) /* default High if possible */
> +#define CEU_FLAG_VSYNC_LOW      (1 << 3)
> +#define CEU_FLAG_LOWER_8BIT     (1 << 4) /* default upper 8bit */
> +#define CEU_FLAG_PRIMARY_SENS	(1 << 5) /* sensor is primary */
> +
> +#define CEU_MAX_SENS		2
> +
> +struct device;
> +struct resource;
> +
> +struct ceu_async_subdev {
> +	unsigned long flags;
> +	unsigned int i2c_adapter_id;
> +	unsigned int i2c_address;
> +
> +};
> +
> +struct ceu_info {
> +	unsigned int num_subdevs;
> +	struct ceu_async_subdev subdevs[CEU_MAX_SENS];
> +};

You're renaming structures and macros that are in use, this will break 
compilation when bisecting.

> +#endif /* __ASM_RENESAS_CEU_H__ */
> diff --git a/include/media/drv-intf/sh_mobile_ceu.h
> b/include/media/drv-intf/sh_mobile_ceu.h deleted file mode 100644
> index 2f43f7d..0000000
> --- a/include/media/drv-intf/sh_mobile_ceu.h
> +++ /dev/null
> @@ -1,28 +0,0 @@
> -#ifndef __ASM_SH_MOBILE_CEU_H__
> -#define __ASM_SH_MOBILE_CEU_H__
> -
> -#define SH_CEU_FLAG_USE_8BIT_BUS	(1 << 0) /* use  8bit bus width */
> -#define SH_CEU_FLAG_USE_16BIT_BUS	(1 << 1) /* use 16bit bus width */
> -#define SH_CEU_FLAG_HSYNC_LOW		(1 << 2) /* default High if 
possible */
> -#define SH_CEU_FLAG_VSYNC_LOW		(1 << 3) /* default High if 
possible */
> -#define SH_CEU_FLAG_LOWER_8BIT		(1 << 4) /* default upper 8bit 
*/
> -
> -struct device;
> -struct resource;
> -
> -struct sh_mobile_ceu_companion {
> -	u32		num_resources;
> -	struct resource	*resource;
> -	int		id;
> -	void		*platform_data;
> -};
> -
> -struct sh_mobile_ceu_info {
> -	unsigned long flags;
> -	int max_width;
> -	int max_height;
> -	struct v4l2_async_subdev **asd;	/* Flat array, arranged in groups */
> -	unsigned int *asd_sizes;	/* 0-terminated array pf asd group 
sizes */
> -};
> -
> -#endif /* __ASM_SH_MOBILE_CEU_H__ */

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2017-05-20 11:59 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-05-19 16:02 [RFC v4 0/8] Renesas CEU driver Jacopo Mondi
2017-05-19 16:02 ` [RFC v4 1/8] include: media: Move and update CEU driver interface Jacopo Mondi
2017-05-20 11:59   ` Laurent Pinchart
2017-05-19 16:02 ` [RFC v4 2/8] media: platform: soc-camera: Remove SH CEU driver Jacopo Mondi
2017-05-19 16:02 ` [RFC v4 3/8] media: platform: Add Renesas " Jacopo Mondi
2017-05-19 16:02 ` [RFC v4 4/8] media: platform: ceu: Support for multiple subdevs Jacopo Mondi
2017-05-19 16:02 ` [RFC v4 5/8] media: i2c: ov772x: Force use of SCCB protocol Jacopo Mondi
2017-05-19 16:02 ` [RFC v4 6/8] media: i2c: ov772x: Remove soc_camera dependencies Jacopo Mondi
2017-05-19 16:02 ` [RFC v4 7/8] media: i2c: tw9910: " Jacopo Mondi
2017-05-19 16:03 ` [RFC v4 8/8] arch: sh: migor: Use new CEU camera driver Jacopo Mondi

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.