linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v1 00/10] Renesas Capture Engine Unit (CEU) V4L2 driver
@ 2017-11-15 10:55 Jacopo Mondi
  2017-11-15 10:55 ` [PATCH v1 01/10] dt-bindings: media: Add Renesas CEU bindings Jacopo Mondi
                   ` (9 more replies)
  0 siblings, 10 replies; 56+ messages in thread
From: Jacopo Mondi @ 2017-11-15 10:55 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil
  Cc: Jacopo Mondi, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hello,
   this series implementes a modern V4L2 driver for Renesas Capture Engine
Unit (CEU). CEU is currently supported by the soc_camera based driver
drivers/media/platform/soc_camera/sh_mobile_ceu_camera.c

The driver supports capturing images in planar formats (NV12/21 and NV16/61)
and non-planar YUYV422 format.

It had been tested with OV7670/OV7725 images sensor capturing images at
different resolutions (VGA and QVGA)

The series:
- Adds a new driver under drivers/media/platform/renesas-ceu.c and a new driver
  interface under include/media/drv-intf/renesas-ceu.h
- Adds device tree bindings for renesas-ceu
- Adds CEU to Renesas RZ/A1 dtsi
- Ports Migo-R SH4 based platform to make use of the new driver
- Ports image sensor drivers used by Migo-R (ov772x and tw9910) away from
  soc_camera

While this driver aims to replace the existing one, which is the last platform
driver making use of soc_camera framework, this series does not delete any of
the existing code, just because there are other SH4 users of the existing
soc_camera based driver: (mach-ap325rxa, mach-ecovec24, mach-kfr2r09 and
mach-se/7724)

As I only have access to Migo-R board, I have ported that one first, while all
other boards can be compile-ported later, once this new driver will eventually
be accepted mainline.

This series is based on v4.14-rc8 with a few patches applied on top:
https://www.spinics.net/lists/linux-sh/msg51739.html

These patches are required for mainline Migo-R board and sh_mobile_ceu_camera
driver to work properly with SH4 architecture on modern kernels, and I have
based my series on top of them.

A tag with those patches already applied on top of v4.14-rc8 is available at
git://jmondi.org/linux v4.14-rc8-migor-ceu-base

A note on testing:
The CEU IP block is found on both Renesas RZ series devices (single core ARM
platforms) and on older SH4 devices (such as Migo-R).
I have developed and tested the driver on RZ platforms, specifically on
GR-Peach with an OV7670 based camera module. More details on:
https://elinux.org/RZ-A/Boards/GR-PEACH-audiocamerashield

As we aim to replace the soc_camera based driver, I have also tested the
new one on Migo-R, capturing images from the OV7725 sensor installed on
that board (I've not been able to test TW9910 video decoder as the sensor does
not probe on the platform I have access to).
Hans, as you told me, you have a Migo-R and if you eventually would like to
give this series a spin on that platform feel free to ping me, as to run a
modern mainline kernel on SH4 you may need the above mentioned patches and some
attention to configuration option for SH4.

A note on sensor drivers:
As I need ov772x and tw9910 driver to be ported away from soc_camera for testing
on Migo-R, for each of them I have copied the driver first in
drivers/media/i2c/ from drivers/media/i2c/soc_camera without any modification
and then removed soc_camera dependencies in a separate commit to ease review.
As per the soc_camera based CEU platform driver, I have not removed the original
soc_camera based sensor drivers in this series.

Output of v4l2-compliance is available at:
https://paste.debian.net/995838/
I'm slightly confused about what the test application complains for
TRY_FMT/S_FMT but I judged this good enough for a first submission.

Thanks
  j

Jacopo Mondi (10):
  dt-bindings: media: Add Renesas CEU bindings
  include: media: Add Renesas CEU driver interface
  v4l: platform: Add Renesas CEU driver
  ARM: dts: r7s72100: Add Capture Engine Unit (CEU)
  arch: sh: migor: Use new renesas-ceu camera driver
  sh: sh7722: Rename CEU clock
  v4l: i2c: Copy ov772x soc_camera sensor driver
  media: i2c: ov772x: Remove soc_camera dependencies
  v4l: i2c: Copy tw9910 soc_camera sensor driver
  media: i2c: tw9910: Remove soc_camera dependencies

 .../devicetree/bindings/media/renesas,ceu.txt      |   87 +
 arch/arm/boot/dts/r7s72100.dtsi                    |   12 +-
 arch/sh/boards/mach-migor/setup.c                  |  164 +-
 arch/sh/kernel/cpu/sh4a/clock-sh7722.c             |    2 +-
 drivers/media/i2c/Kconfig                          |   21 +
 drivers/media/i2c/Makefile                         |    2 +
 drivers/media/i2c/ov772x.c                         | 1156 +++++++++++++
 drivers/media/i2c/tw9910.c                         | 1037 ++++++++++++
 drivers/media/platform/Kconfig                     |    9 +
 drivers/media/platform/Makefile                    |    2 +
 drivers/media/platform/renesas-ceu.c               | 1766 ++++++++++++++++++++
 include/media/drv-intf/renesas-ceu.h               |   23 +
 include/media/i2c/ov772x.h                         |    3 +
 include/media/i2c/tw9910.h                         |    6 +
 14 files changed, 4199 insertions(+), 91 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/media/renesas,ceu.txt
 create mode 100644 drivers/media/i2c/ov772x.c
 create mode 100644 drivers/media/i2c/tw9910.c
 create mode 100644 drivers/media/platform/renesas-ceu.c
 create mode 100644 include/media/drv-intf/renesas-ceu.h

--
2.7.4

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

* [PATCH v1 01/10] dt-bindings: media: Add Renesas CEU bindings
  2017-11-15 10:55 [PATCH v1 00/10] Renesas Capture Engine Unit (CEU) V4L2 driver Jacopo Mondi
@ 2017-11-15 10:55 ` Jacopo Mondi
  2017-11-15 11:32   ` Kieran Bingham
                     ` (2 more replies)
  2017-11-15 10:55 ` [PATCH v1 02/10] include: media: Add Renesas CEU driver interface Jacopo Mondi
                   ` (8 subsequent siblings)
  9 siblings, 3 replies; 56+ messages in thread
From: Jacopo Mondi @ 2017-11-15 10:55 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil
  Cc: Jacopo Mondi, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Add bindings documentation for Renesas Capture Engine Unit (CEU).

Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
---
 .../devicetree/bindings/media/renesas,ceu.txt      | 87 ++++++++++++++++++++++
 1 file changed, 87 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/renesas,ceu.txt

diff --git a/Documentation/devicetree/bindings/media/renesas,ceu.txt b/Documentation/devicetree/bindings/media/renesas,ceu.txt
new file mode 100644
index 0000000..a88e9cb
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/renesas,ceu.txt
@@ -0,0 +1,87 @@
+Renesas Capture Engine Unit (CEU)
+----------------------------------------------
+
+The Capture Engine Unit is the image capture interface found on Renesas
+RZ chip series and on SH Mobile ones.
+
+The interface supports a single parallel input with up 8/16bits data bus width.
+
+Required properties:
+- compatible
+	Must be "renesas,renesas-ceu".
+- reg
+	Physical address base and size.
+- interrupts
+	The interrupt line number.
+- pinctrl-names, pinctrl-0
+	phandle of pin controller sub-node configuring pins for CEU operations.
+
+CEU supports a single parallel input and should contain a single 'port' subnode
+with a single 'endpoint'. Optional endpoint properties applicable to parallel
+input bus are described in "video-interfaces.txt".
+
+Example:
+
+The example describes the connection between the Capture Engine Unit and a
+OV7670 image sensor sitting on bus i2c1 with an on-board 24Mhz clock.
+
+ceu: ceu@e8210000 {
+	reg = <0xe8210000 0x209c>;
+	compatible = "renesas,renesas-ceu";
+	interrupts = <GIC_SPI 332 IRQ_TYPE_LEVEL_HIGH>;
+	pinctrl-names = "default";
+	pinctrl-0 = <&vio_pins>;
+
+	status = "okay";
+
+	port {
+		ceu_in: endpoint {
+			remote-endpoint = <&ov7670_out>;
+
+			bus-width = <8>;
+			hsync-active = <1>;
+			vsync-active = <1>;
+			pclk-sample = <1>;
+			data-active = <1>;
+		};
+	};
+};
+
+i2c1: i2c@fcfee400 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+
+	status = "okay";
+	clock-frequency = <100000>;
+
+	ov7670: camera@21 {
+		compatible = "ovti,ov7670";
+		reg = <0x21>;
+
+		pinctrl-names = "default";
+		pinctrl-0 = <&vio_pins>;
+
+		reset-gpios = <&port3 11 GPIO_ACTIVE_LOW>;
+		powerdown-gpios = <&port3 12 GPIO_ACTIVE_HIGH>;
+
+		clocks = <&xclk>;
+		clock-names = "xclk";
+
+		xclk: fixed_clk {
+			compatible = "fixed-clock";
+			#clock-cells = <0>;
+			clock-frequency = <24000000>;
+		};
+
+		port {
+			ov7670_out: endpoint {
+				remote-endpoint = <&ceu_in>;
+
+				bus-width = <8>;
+				hsync-active = <1>;
+				vsync-active = <1>;
+				pclk-sample = <1>;
+				data-active = <1>;
+			};
+		};
+	};
-- 
2.7.4

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

* [PATCH v1 02/10] include: media: Add Renesas CEU driver interface
  2017-11-15 10:55 [PATCH v1 00/10] Renesas Capture Engine Unit (CEU) V4L2 driver Jacopo Mondi
  2017-11-15 10:55 ` [PATCH v1 01/10] dt-bindings: media: Add Renesas CEU bindings Jacopo Mondi
@ 2017-11-15 10:55 ` Jacopo Mondi
  2017-11-15 12:36   ` Sakari Ailus
  2017-11-15 10:55 ` [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver Jacopo Mondi
                   ` (7 subsequent siblings)
  9 siblings, 1 reply; 56+ messages in thread
From: Jacopo Mondi @ 2017-11-15 10:55 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil
  Cc: Jacopo Mondi, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Add renesas-ceu header file.

Do not remove the existing sh_mobile_ceu.h one as long as the original
driver does not go away.

Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
---
 include/media/drv-intf/renesas-ceu.h | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)
 create mode 100644 include/media/drv-intf/renesas-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..f2da78c
--- /dev/null
+++ b/include/media/drv-intf/renesas-ceu.h
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0+
+#ifndef __ASM_RENESAS_CEU_H__
+#define __ASM_RENESAS_CEU_H__
+
+#include <media/v4l2-mediabus.h>
+
+#define CEU_FLAG_PRIMARY_SENS	BIT(0)
+#define CEU_MAX_SENS		2
+
+struct ceu_async_subdev {
+	unsigned long flags;
+	unsigned char bus_width;
+	unsigned char bus_shift;
+	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__ */
--
2.7.4

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

* [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-11-15 10:55 [PATCH v1 00/10] Renesas Capture Engine Unit (CEU) V4L2 driver Jacopo Mondi
  2017-11-15 10:55 ` [PATCH v1 01/10] dt-bindings: media: Add Renesas CEU bindings Jacopo Mondi
  2017-11-15 10:55 ` [PATCH v1 02/10] include: media: Add Renesas CEU driver interface Jacopo Mondi
@ 2017-11-15 10:55 ` Jacopo Mondi
  2017-11-15 12:45   ` Sakari Ailus
                     ` (2 more replies)
  2017-11-15 10:55 ` [PATCH v1 04/10] ARM: dts: r7s72100: Add Capture Engine Unit (CEU) Jacopo Mondi
                   ` (6 subsequent siblings)
  9 siblings, 3 replies; 56+ messages in thread
From: Jacopo Mondi @ 2017-11-15 10:55 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil
  Cc: Jacopo Mondi, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Add driver for Renesas Capture Engine Unit (CEU).

The CEU interface supports capturing 'data' (YUV422) and 'images'
(NV[12|21|16|61]).

This driver aims to replace the soc_camera based sh_mobile_ceu one.

Tested with ov7670 camera sensor, providing YUYV_2X8 data on Renesas RZ
platform GR-Peach.

Tested with ov7725 camera sensor on SH4 platform Migo-R.

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

diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 3c4f7fa..401caea 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -144,6 +144,15 @@ config VIDEO_STM32_DCMI
 	  To compile this driver as a module, choose M here: the module
 	  will be called stm32-dcmi.

+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
+	select V4L2_FWNODE
+	---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 327f80a..0d1f02b 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -27,6 +27,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_MUX)			+= video-mux.o
diff --git a/drivers/media/platform/renesas-ceu.c b/drivers/media/platform/renesas-ceu.c
new file mode 100644
index 0000000..aaba3cd
--- /dev/null
+++ b/drivers/media/platform/renesas-ceu.c
@@ -0,0 +1,1768 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * V4L2 Driver for Renesas Capture Engine Unit (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/of_graph.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-fwnode.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-image-sizes.h>
+#include <media/v4l2-mediabus.h>
+#include <media/videobuf2-dma-contig.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_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_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_CFWCR	0x5c /* Firewall operation control register	*/
+#define CEU_CDOCR	0x64 /* Capture data output control 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		*/
+
+/* Data synchronous fetch mode */
+#define CEU_CAMCR_JPEG			BIT(4)
+
+/* Input components ordering: CEU_CAMCR.DTARY field */
+#define CEU_CAMCR_DTARY_8_UYVY		(0x00 << 8)
+#define CEU_CAMCR_DTARY_8_VYUY		(0x01 << 8)
+#define CEU_CAMCR_DTARY_8_YUYV		(0x02 << 8)
+#define CEU_CAMCR_DTARY_8_YVYU		(0x03 << 8)
+/* TODO: input components ordering for 16 bits input */
+
+/* Bus transfer MTU */
+#define CEU_CAPCR_BUS_WIDTH256		(0x3 << 20)
+
+/* Bus width configuration */
+#define CEU_CAMCR_DTIF_16BITS		BIT(12)
+
+/* No downsampling to planar YUV420 in image fetch mode */
+#define CEU_CDOCR_NO_DOWSAMPLE		BIT(4)
+
+/* Swap all input data in 8-bit, 16-bits and 32-bits units (Figure 46.45) */
+#define CEU_CDOCR_SWAP_ENDIANNESS	(7)
+
+/* Capture reset and enable bits */
+#define CEU_CAPSR_CPKIL			BIT(16)
+#define CEU_CAPSR_CE			BIT(0)
+
+/* CEU operating flag bit */
+#define CEU_CAPCR_CTNCP			BIT(16)
+#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)
+#define CEU_CEIER_MASK			(CEU_CEIER_CPE | CEU_CEIER_VBP)
+
+/* mbus configuration flags */
+#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
+ * @bpp: number of bits per pixels unit
+ */
+struct ceu_mbus_fmt {
+	u32	mbus_code;
+	u32	fmt_order;
+	u32	fmt_order_swap;
+	bool	swapped;
+	u8	bps;
+	u8	bpp;
+};
+
+/**
+ * ceu_buffer - Link vb2 buffer to the list of available 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_subdev - Wraps v4l2 sub-device and provides async subdevice.
+ */
+struct ceu_subdev {
+	struct v4l2_subdev *v4l2_sd;
+	struct v4l2_async_subdev asd;
+
+	/* per-subdevice mbus configuration options */
+	unsigned int mbus_flags;
+	struct ceu_mbus_fmt mbus_fmt;
+};
+
+static struct ceu_subdev *to_ceu_subdev(struct v4l2_async_subdev *asd)
+{
+	return container_of(asd, struct ceu_subdev, asd);
+}
+
+/**
+ * ceu_device - CEU device instance
+ */
+struct ceu_device {
+	struct device		*dev;
+	struct video_device	vdev;
+	struct v4l2_device	v4l2_dev;
+
+	/* subdevices descriptors */
+	struct ceu_subdev	*subdevs;
+	/* the subdevice currently in use */
+	struct ceu_subdev	*sd;
+	unsigned int		sd_index;
+	unsigned int		num_sd;
+
+	/* currently configured field and pixel format */
+	enum v4l2_field	field;
+	struct v4l2_pix_format_mplane v4l2_pix;
+
+	/* async subdev notification helpers */
+	struct v4l2_async_notifier notifier;
+	/* pointers to "struct ceu_subdevice -> asd" */
+	struct v4l2_async_subdev **asds;
+
+	/* 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;
+
+	/* base - CEU memory base address */
+	void __iomem	*base;
+};
+
+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
+ * capabilities.
+ */
+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,
+	},
+	{
+		.fourcc	= V4L2_PIX_FMT_YUYV,
+		.bpp	= 16,
+	},
+};
+
+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;
+}
+
+static inline bool ceu_is_fmt_planar(struct v4l2_pix_format_mplane *pix)
+{
+	switch (pix->pixelformat) {
+	case V4L2_PIX_FMT_YUYV:
+		return false;
+	case V4L2_PIX_FMT_NV16:
+	case V4L2_PIX_FMT_NV61:
+	case V4L2_PIX_FMT_NV12:
+	case V4L2_PIX_FMT_NV21:
+		return true;
+	}
+
+	return true;
+}
+
+/* ----------------------------------------------------------------------------
+ * 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
+ */
+static int ceu_soft_reset(struct ceu_device *ceudev)
+{
+	unsigned int reset_done;
+	unsigned int i;
+
+	ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CPKIL);
+
+	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) {
+		v4l2_err(&ceudev->v4l2_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 capture.
+ */
+static int ceu_capture(struct ceu_device *ceudev)
+{
+	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
+	dma_addr_t phys_addr_top;
+	u32 status;
+
+	/* Clean interrupt status and re-enable interrupts */
+	status = ceu_read(ceudev, CEU_CETCR);
+	ceu_write(ceudev, CEU_CEIER,
+		  ceu_read(ceudev, CEU_CEIER) & ~CEU_CEIER_MASK);
+	ceu_write(ceudev, CEU_CETCR, ~status & CEU_CETCR_MAGIC);
+	ceu_write(ceudev, CEU_CEIER, CEU_CEIER_MASK);
+	ceu_write(ceudev, CEU_CAPCR,
+		  ceu_read(ceudev, CEU_CAPCR) & ~CEU_CAPCR_CTNCP);
+
+	/*
+	 * 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) {
+		v4l2_err(&ceudev->v4l2_dev,
+			 "VBP interrupt while capturing\n");
+		ceu_soft_reset(ceudev);
+		return -EIO;
+	} else if (!ceudev->active) {
+		v4l2_err(&ceudev->v4l2_dev,
+			 "No available buffers for capture\n");
+		return -EINVAL;
+	}
+
+	phys_addr_top =
+		vb2_dma_contig_plane_dma_addr(&ceudev->active->vb2_buf, 0);
+	ceu_write(ceudev, CEU_CDAYR, phys_addr_top);
+
+	/* Ignore CbCr plane in data sync mode */
+	if (ceu_is_fmt_planar(pix)) {
+		phys_addr_top =
+			vb2_dma_contig_plane_dma_addr(&ceudev->active->vb2_buf,
+						      1);
+		ceu_write(ceudev, CEU_CDACR, phys_addr_top);
+	}
+
+	/*
+	 * 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 0;
+}
+
+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;
+
+	/* Prepare a new 'active' buffer and trigger a new capture */
+	buf = vb2_to_ceu(vbuf);
+	vbuf->vb2_buf.timestamp = ktime_get_ns();
+
+	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;
+	}
+
+	/*
+	 * If the new capture started successfully, mark the previous buffer
+	 * as "DONE".
+	 */
+	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_calc_plane_sizes() - Fill 'struct v4l2_plane_pix_format' per plane
+ *			    information according to the currently configured
+ *			    pixel format.
+ */
+static int ceu_calc_plane_sizes(struct ceu_device *ceudev,
+				const struct ceu_fmt *ceu_fmt,
+				struct v4l2_pix_format_mplane *pix)
+{
+	struct v4l2_plane_pix_format *plane_fmt = &pix->plane_fmt[0];
+
+	switch (pix->pixelformat) {
+	case V4L2_PIX_FMT_YUYV:
+		pix->num_planes			= 1;
+		plane_fmt[0].bytesperline	= pix->width * ceu_fmt->bpp / 8;
+		plane_fmt[0].sizeimage		= pix->height *
+						  plane_fmt[0].bytesperline;
+		break;
+	case V4L2_PIX_FMT_NV16:
+	case V4L2_PIX_FMT_NV61:
+		pix->num_planes			= 2;
+		plane_fmt[0].bytesperline	= pix->width;
+		plane_fmt[0].sizeimage		= pix->height * pix->width;
+		plane_fmt[1]			= plane_fmt[0];
+		break;
+	case V4L2_PIX_FMT_NV12:
+	case V4L2_PIX_FMT_NV21:
+		pix->num_planes			= 2;
+		plane_fmt[0].bytesperline	= pix->width;
+		plane_fmt[0].sizeimage		= pix->height * pix->width;
+		plane_fmt[1].bytesperline	= pix->width;
+		plane_fmt[1].sizeimage		= pix->height * pix->width / 2;
+		break;
+	default:
+		pix->num_planes			= 0;
+		v4l2_err(&ceudev->v4l2_dev,
+			 "Format 0x%x not supported\n", pix->pixelformat);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*
+ * 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_mplane *pix = &ceudev->v4l2_pix;
+	unsigned int i;
+
+	if (!vq->num_buffers)
+		ceudev->sequence = 0;
+
+	if (!*count)
+		*count = 2;
+
+	/* num_planes is set: just check plane sizes */
+	if (*num_planes) {
+		for (i = 0; i < pix->num_planes; i++) {
+			if (sizes[i] < pix->plane_fmt[i].sizeimage)
+				return -EINVAL;
+		}
+
+		return 0;
+	}
+
+	/* num_planes not set: called from REQBUFS, just set plane sizes */
+	*num_planes = pix->num_planes;
+	for (i = 0; i < pix->num_planes; i++)
+		sizes[i] = pix->plane_fmt[i].sizeimage;
+
+	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 v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
+	struct ceu_buffer *buf = vb2_to_ceu(vbuf);
+	unsigned long irqflags;
+	unsigned int i;
+
+	for (i = 0; i < pix->num_planes; i++) {
+		if (vb2_plane_size(vb, i) < pix->plane_fmt[i].sizeimage) {
+			v4l2_err(&ceudev->v4l2_dev,
+				 "Buffer #%d too small (%lu < %u)\n",
+				 vb->index, vb2_plane_size(vb, i),
+				 pix->plane_fmt[i].sizeimage);
+			goto error;
+		}
+
+		vb2_set_plane_payload(vb, i, pix->plane_fmt[i].sizeimage);
+	}
+
+	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 v4l2_subdev *v4l2_sd = ceudev->sd->v4l2_sd;
+	struct ceu_buffer *buf;
+	unsigned long irqflags;
+	int ret = 0;
+
+	ret = v4l2_subdev_call(v4l2_sd, video, s_stream, 1);
+	if (ret && ret != -ENOIOCTLCMD) {
+		v4l2_err(&ceudev->v4l2_dev,
+			 "Subdevice failed to start streaming: %d\n", ret);
+		goto error_return_bufs;
+	}
+
+	spin_lock_irqsave(&ceudev->lock, irqflags);
+	ceudev->sequence = 0;
+
+	if (ceudev->active) {
+		ret = -EINVAL;
+		spin_unlock_irqrestore(&ceudev->lock, irqflags);
+		goto error_stop_sensor;
+	}
+
+	/* Grab the first available buffer and trigger the first capture. */
+	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(v4l2_sd, 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 v4l2_subdev *v4l2_sd = ceudev->sd->v4l2_sd;
+	struct ceu_buffer *buf;
+	unsigned long irqflags;
+
+	v4l2_subdev_call(v4l2_sd, 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(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+	INIT_LIST_HEAD(&ceudev->capture);
+
+	spin_unlock_irqrestore(&ceudev->lock, irqflags);
+
+	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 errors
+ *	    0 if g_mbus_config is not supported,
+ *	    > 0  for bus configuration flags supported by (ceu AND sensor)
+ */
+static int ceu_test_mbus_param(struct ceu_device *ceudev)
+{
+	struct v4l2_subdev *sd = ceudev->sd->v4l2_sd;
+	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_bus_params() - Configure CEU interface registers using bus
+ *			  parameters
+ */
+static int ceu_set_bus_params(struct ceu_device *ceudev)
+{
+	u32 camcr = 0, cdocr = 0, cfzsr = 0, cdwdr = 0, capwr = 0;
+	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
+	struct ceu_subdev *ceu_sd = ceudev->sd;
+	struct ceu_mbus_fmt *mbus_fmt = &ceu_sd->mbus_fmt;
+	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
+	unsigned int mbus_flags = ceu_sd->mbus_flags;
+	unsigned long common_flags = CEU_BUS_FLAGS;
+	int ret;
+	struct v4l2_mbus_config cfg = {
+		.type = V4L2_MBUS_PARALLEL,
+	};
+
+	/*
+	 * If client doesn't implement g_mbus_config, we just use our
+	 * platform data.
+	 */
+	ret = ceu_test_mbus_param(ceudev);
+	if (ret < 0)
+		return ret;
+	else if (ret == 0)
+		common_flags = ceudev->sd->mbus_flags;
+	else
+		common_flags = ret;
+
+	/*
+	 * If the we can choose between multiple alternatives select
+	 * active high polarities.
+	 */
+	if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) &&
+	    (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) {
+		if (mbus_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
+			common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW;
+		else
+			common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH;
+	}
+
+	if ((common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) &&
+	    (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) {
+		if (mbus_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
+			common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_LOW;
+		else
+			common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_HIGH;
+	}
+
+	cfg.flags = common_flags;
+	ret = v4l2_subdev_call(v4l2_sd, video, s_mbus_config, &cfg);
+	if (ret < 0 && ret != -ENOIOCTLCMD)
+		return ret;
+
+	/* Start configuring CEU registers */
+	ceu_write(ceudev, CEU_CAIFR, 0);
+	ceu_write(ceudev, CEU_CFWCR, 0);
+	ceu_write(ceudev, CEU_CRCNTR, 0);
+	ceu_write(ceudev, CEU_CRCMPR, 0);
+
+	/* Set the frame capture period for both image capture and data sync */
+	capwr = (pix->height << 16) | pix->width * mbus_fmt->bpp / 8;
+
+	/*
+	 * Swap input data endianness by default.
+	 * 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)
+	 *
+	 * Use CEU_CDOCR[2:0] to swap data ordering.
+	 */
+	cdocr = CEU_CDOCR_SWAP_ENDIANNESS;
+
+	/*
+	 * Configure CAMCR and CDOCR:
+	 * match input components ordering with memory output format and
+	 * handle downsampling to YUV420.
+	 *
+	 * If the memory output planar format is 'swapped' (Cr before Cb) and
+	 * input format is not, use the swapped version of CAMCR.DTARY.
+	 *
+	 * If the memory output planar 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
+	 *
+	 * No downsample for data fetch sync mode.
+	 */
+	switch (pix->pixelformat) {
+	/* data fetch sync mode */
+	case V4L2_PIX_FMT_YUYV:
+		/* TODO: handle YUYV permutations through DTARY bits */
+		camcr	|= CEU_CAMCR_JPEG;
+		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
+		cfzsr	= (pix->height << 16) | pix->width;
+		cdwdr	= pix->plane_fmt[0].bytesperline;
+		break;
+
+	/* non-swapped planar image capture mode */
+	case V4L2_PIX_FMT_NV16:
+		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
+	case V4L2_PIX_FMT_NV12:
+		if (mbus_fmt->swapped)
+			camcr |= mbus_fmt->fmt_order_swap;
+		else
+			camcr |= mbus_fmt->fmt_order;
+
+		cfzsr	= (pix->height << 16) | pix->width;
+		cdwdr	= pix->width;
+		break;
+
+	/* swapped planar image capture mode */
+	case V4L2_PIX_FMT_NV61:
+		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
+	case V4L2_PIX_FMT_NV21:
+		if (mbus_fmt->swapped)
+			camcr |= mbus_fmt->fmt_order;
+		else
+			camcr |= mbus_fmt->fmt_order_swap;
+
+		cfzsr	= (pix->height << 16) | pix->width;
+		cdwdr	= pix->width;
+		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 with DTIF bit in CAMCR */
+	ceu_write(ceudev, CEU_CAMCR, camcr);
+	ceu_write(ceudev, CEU_CDOCR, cdocr);
+	ceu_write(ceudev, CEU_CAPCR, CEU_CAPCR_BUS_WIDTH256);
+
+	/*
+	 * TODO: make CAMOR offsets configurable.
+	 * CAMOR wants to know the number of blanks between a VS/HS signal
+	 * and valid data. This value should actually come from the sensor...
+	 */
+	ceu_write(ceudev, CEU_CAMOR, 0);
+
+	/* TODO: 16 bit bus width require re-calculation of cdwdr and cfzsr */
+	ceu_write(ceudev, CEU_CAPWR, capwr);
+	ceu_write(ceudev, CEU_CFSZR, cfzsr);
+	ceu_write(ceudev, CEU_CDWDR, cdwdr);
+
+	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 v4l2_pix_format_mplane *pix = &v4l2_fmt->fmt.pix_mp;
+	struct ceu_subdev *ceu_sd = ceudev->sd;
+	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
+	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_YUYV:
+	case V4L2_PIX_FMT_NV16:
+	case V4L2_PIX_FMT_NV61:
+	case V4L2_PIX_FMT_NV12:
+	case V4L2_PIX_FMT_NV21:
+		break;
+
+	default:
+		v4l2_err(&ceudev->v4l2_dev,
+			 "Pixel format 0x%x not supported, default to NV16\n",
+			 pix->pixelformat);
+		pix->pixelformat = V4L2_PIX_FMT_NV16;
+	}
+
+	ceu_fmt = get_ceu_fmt_from_fourcc(pix->pixelformat);
+
+	/* 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 used to produce memory
+	 * format is selected at initialization time
+	 */
+	v4l2_fill_mbus_format_mplane(&sd_format.format, pix);
+	ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, &pad_cfg, &sd_format);
+	if (ret)
+		return ret;
+
+	/* Scale down to sensor supported sizes */
+	if (sd_format.format.width != pix->width ||
+	    sd_format.format.height != pix->height) {
+		v4l2_err(&ceudev->v4l2_dev,
+			 "Unable to apply: 0x%x - %ux%u - scale to %ux%u\n",
+			 pix->pixelformat, pix->width, pix->height,
+			 sd_format.format.width, sd_format.format.height);
+		pix->width = sd_format.format.width;
+		pix->height = sd_format.format.height;
+	}
+
+	/* Calculate per-plane sizes based on image format */
+	v4l2_fill_pix_format_mplane(pix, &sd_format.format);
+	pix->field = V4L2_FIELD_NONE;
+	ret = ceu_calc_plane_sizes(ceudev, ceu_fmt, pix);
+	if (ret < 0)
+		return ret;
+
+	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_subdev *ceu_sd = ceudev->sd;
+	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
+	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_mplane(&format.format, &v4l2_fmt->fmt.pix_mp);
+	ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, NULL, &format);
+	if (ret)
+		return ret;
+
+	ceudev->v4l2_pix = v4l2_fmt->fmt.pix_mp;
+
+	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_MPLANE,
+		.fmt.pix_mp = {
+			.width		= VGA_WIDTH,
+			.height		= VGA_HEIGHT,
+			.field		= V4L2_FIELD_NONE,
+			.pixelformat	= V4L2_PIX_FMT_NV16,
+			.plane_fmt	= {
+				[0]	= {
+					.sizeimage = VGA_WIDTH * VGA_HEIGHT * 2,
+					.bytesperline = VGA_WIDTH * 2,
+				},
+				[1]	= {
+					.sizeimage = VGA_WIDTH * VGA_HEIGHT * 2,
+					.bytesperline = VGA_WIDTH * 2,
+				},
+			},
+		},
+	};
+
+	ret = ceu_try_fmt(ceudev, &v4l2_fmt);
+	if (ret)
+		return ret;
+
+	ceudev->v4l2_pix = v4l2_fmt.fmt.pix_mp;
+
+	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;
+ * - YUYV422 if sensor provides YUYV422
+ *
+ * TODO: Other YUYV422 permutations throug data fetch sync mode and DTARY
+ * TODO: Binary data (eg. JPEG) and raw formats through data fetch sync mode
+ */
+static int ceu_init_formats(struct ceu_device *ceudev)
+{
+	struct ceu_subdev *ceu_sd = ceudev->sd;
+	struct ceu_mbus_fmt *mbus_fmt = &ceu_sd->mbus_fmt;
+	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
+	bool yuyv_bus_fmt = false;
+
+	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 YUYV422 */
+	while (!yuyv_bus_fmt &&
+	       !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:
+		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;
+		}
+
+		sd_mbus_fmt.index++;
+	}
+
+	if (!yuyv_bus_fmt)
+		return -ENXIO;
+
+	/*
+	 * Save the first encountered YUYV format as "mbus_fmt" and use it
+	 * to output all planar YUV422 and YUV420 (NV*) formats to memory as
+	 * well as for data synch fetch mode (YUYV - YVYU etc. ).
+	 */
+	mbus_fmt->mbus_code	= sd_mbus_fmt.code;
+	mbus_fmt->bps		= 8;
+
+	/* Annotate the selected bus format components ordering */
+	switch (sd_mbus_fmt.code) {
+	case MEDIA_BUS_FMT_YUYV8_2X8:
+		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_YUYV;
+		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_YVYU;
+		mbus_fmt->swapped		= false;
+		mbus_fmt->bpp			= 16;
+		break;
+
+	case MEDIA_BUS_FMT_YVYU8_2X8:
+		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_YVYU;
+		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_YUYV;
+		mbus_fmt->swapped		= true;
+		mbus_fmt->bpp			= 16;
+		break;
+
+	case MEDIA_BUS_FMT_UYVY8_2X8:
+		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_UYVY;
+		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_VYUY;
+		mbus_fmt->swapped		= false;
+		mbus_fmt->bpp			= 16;
+		break;
+
+	case MEDIA_BUS_FMT_VYUY8_2X8:
+		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_VYUY;
+		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_UYVY;
+		mbus_fmt->swapped		= true;
+		mbus_fmt->bpp			= 16;
+		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 *v4l2_sd = ceudev->sd->v4l2_sd;
+
+	v4l2_subdev_call(v4l2_sd, 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 *v4l2_sd = ceudev->sd->v4l2_sd;
+
+	v4l2_subdev_call(v4l2_sd, 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_g_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;
+
+	f->type	= V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+	f->fmt.pix_mp = ceudev->v4l2_pix;
+
+	return 0;
+}
+
+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)
+{
+	struct ceu_device *ceudev = video_drvdata(file);
+
+	*i = ceudev->sd_index;
+
+	return 0;
+}
+
+static int ceu_s_input(struct file *file, void *priv, unsigned int i)
+{
+	struct ceu_device *ceudev = video_drvdata(file);
+	struct ceu_subdev *ceu_sd_old;
+	int ret;
+
+	if (i >= ceudev->num_sd)
+		return -EINVAL;
+
+	ceu_sd_old = ceudev->sd;
+	ceudev->sd = &ceudev->subdevs[i];
+
+	/* Make sure we can generate output image formats. */
+	ret = ceu_init_formats(ceudev);
+	if (ret) {
+		ceudev->sd = 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_sd, core, s_power, 0);
+	v4l2_subdev_call(ceudev->sd->v4l2_sd, core, s_power, 1);
+
+	ceudev->sd_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);
+
+	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+		return -EINVAL;
+
+	return v4l2_subdev_call(ceudev->sd->v4l2_sd, 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_MPLANE)
+		return -EINVAL;
+
+	return v4l2_subdev_call(ceudev->sd->v4l2_sd, 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->sd;
+	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
+	int ret;
+
+	struct v4l2_subdev_frame_size_enum fse = {
+		.code	= ceu_sd->mbus_fmt.mbus_code,
+		.index	= fsize->index,
+		.which	= V4L2_SUBDEV_FORMAT_ACTIVE,
+	};
+
+	ret = v4l2_subdev_call(v4l2_sd, 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);
+	struct ceu_subdev *ceu_sd = ceudev->sd;
+	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
+	int ret;
+
+	struct v4l2_subdev_frame_interval_enum fie = {
+		.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(v4l2_sd, 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_mplane	= ceu_enum_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap_mplane	= ceu_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap_mplane	= ceu_s_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap_mplane	= ceu_g_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 *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(asd);
+
+	if (video_is_registered(&ceudev->vdev)) {
+		v4l2_err(&ceudev->v4l2_dev,
+			 "Video device registered before this sub-device.\n");
+		return -EBUSY;
+	}
+
+	/* Assign subdevices in the order they appear */
+	ceu_sd->v4l2_sd = v4l2_sd;
+	ceudev->num_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 */
+	q->type			= V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+	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;
+
+	/*
+	 * Make sure at least one sensor is primary and use it to initialize
+	 * ceu formats
+	 */
+	if (!ceudev->sd) {
+		ceudev->sd = &ceudev->subdevs[0];
+		ceudev->sd_index = 0;
+	}
+
+	v4l2_sd = ceudev->sd->v4l2_sd;
+
+	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	= v4l2_sd->ctrl_handler;
+	vdev->fops		= &ceu_fops;
+	vdev->ioctl_ops		= &ceu_ioctl_ops;
+	vdev->release		= ceu_vdev_release;
+	vdev->device_caps	= V4L2_CAP_VIDEO_CAPTURE_MPLANE |
+				  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;
+}
+
+/**
+ * ceu_parse_init_sd() - Initialize CEU subdevices and async_subdevs in
+ *			 ceu device. Both DT and platform data parsing use
+ *			 this routine.
+ *
+ * @return 0 for success, -ENOMEM for failure.
+ */
+static int ceu_parse_init_sd(struct ceu_device *ceudev, unsigned int n_sd)
+{
+	/* Reserve memory for 'n_sd' ceu_subdev descriptors */
+	ceudev->subdevs = devm_kcalloc(ceudev->dev, n_sd,
+				       sizeof(*ceudev->subdevs), GFP_KERNEL);
+	if (!ceudev->subdevs)
+		return -ENOMEM;
+
+	/*
+	 * Reserve memory for 'n_sd' pointers to async_subdevices.
+	 * ceudev->asds members will point to &ceu_subdev.asd
+	 */
+	ceudev->asds = devm_kcalloc(ceudev->dev, n_sd,
+				    sizeof(*ceudev->asds), GFP_KERNEL);
+	if (!ceudev->asds)
+		return -ENOMEM;
+
+	ceudev->sd = NULL;
+	ceudev->sd_index = 0;
+	ceudev->num_sd = 0;
+
+	return 0;
+}
+
+/**
+ * ceu_parse_platform_data() - Initialize async_subdevices using platform
+ *			       device provided data.
+ */
+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;
+	int ret;
+
+	ret = ceu_parse_init_sd(ceudev, info->num_subdevs);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < info->num_subdevs; i++) {
+		/* Setup the ceu subdevice and the async subdevice */
+		async_sd = &info->subdevs[i];
+		ceu_sd = &ceudev->subdevs[i];
+
+		memset(&ceu_sd->asd, 0, sizeof(ceu_sd->asd));
+		INIT_LIST_HEAD(&ceu_sd->asd.list);
+
+		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;
+
+		ceudev->asds[i] = &ceu_sd->asd;
+	}
+
+	return info->num_subdevs;
+}
+
+/**
+ * ceu_parse_dt() - Initialize async_subdevs parsing device tree graph
+ */
+static int ceu_parse_dt(struct ceu_device *ceudev)
+{
+	struct device_node *of = ceudev->dev->of_node;
+	struct v4l2_fwnode_endpoint fw_ep;
+	struct ceu_subdev *ceu_sd;
+	struct device_node *ep;
+	unsigned int i;
+	int num_ep;
+	int ret;
+
+	num_ep = of_graph_get_endpoint_count(of);
+	if (num_ep <= 0)
+		return 0;
+
+	ret = ceu_parse_init_sd(ceudev, num_ep);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < num_ep; i++) {
+		ep = of_graph_get_endpoint_by_regs(of, 0, i);
+		if (!ep) {
+			v4l2_err(&ceudev->v4l2_dev,
+				 "No subdevice connected on port %u.\n", i);
+			ret = -ENODEV;
+			goto error_put_node;
+		}
+
+		ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &fw_ep);
+		if (ret) {
+			v4l2_err(&ceudev->v4l2_dev,
+				 "Unable to parse endpoint #%u.\n", i);
+			goto error_put_node;
+		}
+
+		if (fw_ep.bus_type != V4L2_MBUS_PARALLEL) {
+			v4l2_err(&ceudev->v4l2_dev,
+				 "Only parallel input supported.\n");
+			ret = -EINVAL;
+			goto error_put_node;
+		}
+
+		/* Setup the ceu subdevice and the async subdevice */
+		ceu_sd = &ceudev->subdevs[i];
+		memset(&ceu_sd->asd, 0, sizeof(ceu_sd->asd));
+		INIT_LIST_HEAD(&ceu_sd->asd.list);
+
+		ceu_sd->mbus_flags = fw_ep.bus.parallel.flags;
+		ceu_sd->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
+		ceu_sd->asd.match.fwnode.fwnode =
+			fwnode_graph_get_remote_port_parent(
+					of_fwnode_handle(ep));
+
+		ceudev->asds[i] = &ceu_sd->asd;
+		of_node_put(ep);
+	}
+
+	return num_ep;
+
+error_put_node:
+	of_node_put(ep);
+	return ret;
+}
+
+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 num_sd;
+	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);
+	if (IS_ERR(res))
+		return PTR_ERR(res);
+
+	base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(base))
+		return PTR_ERR(base);
+	ceudev->base = base;
+
+	ret = platform_get_irq(pdev, 0);
+	if (ret < 0) {
+		dev_err(dev, "failed to get irq: %d\n", ret);
+		return ret;
+	}
+	irq = ret;
+
+	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;
+	}
+
+	pm_suspend_ignore_children(dev, true);
+	pm_runtime_enable(dev);
+
+	ret = v4l2_device_register(dev, &ceudev->v4l2_dev);
+	if (ret)
+		goto error_pm_disable;
+
+	if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
+		num_sd = ceu_parse_dt(ceudev);
+	} else if (dev->platform_data) {
+		num_sd = ceu_parse_platform_data(ceudev, dev->platform_data);
+	} else {
+		dev_err(dev, "CEU platform data not set and no OF support\n");
+		ret = -EINVAL;
+		goto error_v4l2_unregister;
+	}
+
+	if (num_sd < 0) {
+		ret = num_sd;
+		goto error_v4l2_unregister;
+	} else if (num_sd == 0)
+		return 0;
+
+	ceudev->notifier.v4l2_dev	= &ceudev->v4l2_dev;
+	ceudev->notifier.subdevs	= ceudev->asds;
+	ceudev->notifier.num_subdevs	= num_sd;
+	ceudev->notifier.bound		= ceu_sensor_bound;
+	ceudev->notifier.complete	= ceu_sensor_complete;
+	ret = v4l2_async_notifier_register(&ceudev->v4l2_dev,
+					   &ceudev->notifier);
+	if (ret)
+		goto error_v4l2_unregister_notifier;
+
+	dev_info(dev, "Renesas Capture Engine Unit\n");
+
+	return 0;
+
+error_v4l2_unregister_notifier:
+	v4l2_async_notifier_unregister(&ceudev->notifier);
+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[] = {
+	{ .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] 56+ messages in thread

* [PATCH v1 04/10] ARM: dts: r7s72100: Add Capture Engine Unit (CEU)
  2017-11-15 10:55 [PATCH v1 00/10] Renesas Capture Engine Unit (CEU) V4L2 driver Jacopo Mondi
                   ` (2 preceding siblings ...)
  2017-11-15 10:55 ` [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver Jacopo Mondi
@ 2017-11-15 10:55 ` Jacopo Mondi
  2017-11-17 14:22   ` Simon Horman
  2017-11-23  9:41   ` Geert Uytterhoeven
  2017-11-15 10:55 ` [PATCH v1 05/10] arch: sh: migor: Use new renesas-ceu camera driver Jacopo Mondi
                   ` (5 subsequent siblings)
  9 siblings, 2 replies; 56+ messages in thread
From: Jacopo Mondi @ 2017-11-15 10:55 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil
  Cc: Jacopo Mondi, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Add Capture Engine Unit (CEU) node to device tree.

Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
---
 arch/arm/boot/dts/r7s72100.dtsi | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/arch/arm/boot/dts/r7s72100.dtsi b/arch/arm/boot/dts/r7s72100.dtsi
index 4ed12a4..683d459 100644
--- a/arch/arm/boot/dts/r7s72100.dtsi
+++ b/arch/arm/boot/dts/r7s72100.dtsi
@@ -136,8 +136,8 @@
 			compatible = "renesas,r7s72100-mstp-clocks", "renesas,cpg-mstp-clocks";
 			reg = <0xfcfe042c 4>;
 			clocks = <&p0_clk>;
-			clock-indices = <R7S72100_CLK_RTC>;
-			clock-output-names = "rtc";
+			clock-indices = <R7S72100_CLK_RTC R7S72100_CLK_CEU>;
+			clock-output-names = "rtc", "ceu";
 		};
 
 		mstp7_clks: mstp7_clks@fcfe0430 {
@@ -666,4 +666,12 @@
 		power-domains = <&cpg_clocks>;
 		status = "disabled";
 	};
+
+	ceu: ceu@e8210000 {
+		reg = <0xe8210000 0x209c>;
+		compatible = "renesas,renesas-ceu";
+		interrupts = <GIC_SPI 332 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&cpg_clocks>;
+		status = "disabled";
+	};
 };
-- 
2.7.4

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

* [PATCH v1 05/10] arch: sh: migor: Use new renesas-ceu camera driver
  2017-11-15 10:55 [PATCH v1 00/10] Renesas Capture Engine Unit (CEU) V4L2 driver Jacopo Mondi
                   ` (3 preceding siblings ...)
  2017-11-15 10:55 ` [PATCH v1 04/10] ARM: dts: r7s72100: Add Capture Engine Unit (CEU) Jacopo Mondi
@ 2017-11-15 10:55 ` Jacopo Mondi
  2017-12-11 14:36   ` Laurent Pinchart
  2017-12-12 10:00   ` Laurent Pinchart
  2017-11-15 10:55 ` [PATCH v1 06/10] sh: sh7722: Rename CEU clock Jacopo Mondi
                   ` (4 subsequent siblings)
  9 siblings, 2 replies; 56+ messages in thread
From: Jacopo Mondi @ 2017-11-15 10:55 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil
  Cc: Jacopo Mondi, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Migo-R platform uses sh_mobile_ceu camera driver, which is now being
replaced by a proper V4L2 camera driver named 'renesas-ceu'.

Move Migo-R platform to use the v4l2 renesas-ceu camera driver
interface and get rid of soc_camera defined components used to register
sensor drivers.

Also, memory for CEU video buffers is now reserved with membocks APIs,
and need to be declared as dma_coherent during machine initialization to
remove that architecture specific part from CEU driver.

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

diff --git a/arch/sh/boards/mach-migor/setup.c b/arch/sh/boards/mach-migor/setup.c
index 98aa094..10a9b3c 100644
--- a/arch/sh/boards/mach-migor/setup.c
+++ b/arch/sh/boards/mach-migor/setup.c
@@ -27,7 +27,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>
@@ -308,62 +308,80 @@ 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.
 	 */
 	camera_clk = clk_get(NULL, "video_clk");
 	clk_set_rate(camera_clk, 10000000);
 	clk_enable(camera_clk);	/* start VIO_CKO */
-
-	/* 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)
+static void camera_disable(void)
 {
-	clk_disable(camera_clk); /* stop VIO_CKO */
+	/* stop VIO_CKO */
+	clk_disable(camera_clk);
 	clk_put(camera_clk);

+	gpio_set_value(GPIO_PTT0, 0);
+	gpio_set_value(GPIO_PTT2, 1);
 	gpio_set_value(GPIO_PTT3, 0);
+
 	mutex_unlock(&camera_lock);
 }

-static int ov7725_power(struct device *dev, int mode)
+static void camera_reset(void)
 {
-	if (mode)
-		camera_power_on(0);
-	else
-		camera_power_off();
+	/* use VIO_RST to take camera out of reset */
+	gpio_set_value(GPIO_PTT3, 0);
+	mdelay(10);
+	gpio_set_value(GPIO_PTT3, 1);
+	mdelay(10);
+}
+
+static int ov7725_enable(void)
+{
+	mutex_lock(&camera_lock);
+	camera_vio_clk_on();
+	mdelay(10);
+	gpio_set_value(GPIO_PTT0, 1);
+
+	camera_reset();

 	return 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();
+	mutex_lock(&camera_lock);
+	camera_vio_clk_on();
+	mdelay(10);
+	gpio_set_value(GPIO_PTT2, 0);
+
+	camera_reset();

 	return 0;
 }

-static struct sh_mobile_ceu_info sh_mobile_ceu_info = {
-	.flags = SH_CEU_FLAG_USE_8BIT_BUS,
+static struct ceu_info ceu_info = {
+	.num_subdevs		= 2,
+	.subdevs = {
+		{ /* [0] = ov772x */
+			.flags		= CEU_FLAG_PRIMARY_SENS,
+			.bus_width	= 8,
+			.bus_shift	= 0,
+			.i2c_adapter_id	= 0,
+			.i2c_address	= 0x21,
+		},
+		{ /* [1] = tw9910 */
+			.flags		= 0,
+			.bus_width	= 8,
+			.bus_shift	= 0,
+			.i2c_adapter_id	= 0,
+			.i2c_address	= 0x45,
+		},
+	},
 };

 static struct resource migor_ceu_resources[] = {
@@ -377,18 +395,15 @@ static struct resource migor_ceu_resources[] = {
 		.start  = evt2irq(0x880),
 		.flags  = IORESOURCE_IRQ,
 	},
-	[2] = {
-		/* place holder for contiguous memory */
-	},
 };

 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,
 	},
 };

@@ -427,6 +442,19 @@ static struct platform_device sdhi_cn9_device = {
 	},
 };

+static struct ov772x_camera_info ov7725_info = {
+	.platform_enable = ov7725_enable,
+	.platform_disable = camera_disable,
+};
+
+static struct tw9910_video_info tw9910_info = {
+	.buswidth       = TW9910_DATAWIDTH_8,
+	.mpout          = TW9910_MPO_FIELD,
+
+	.platform_enable = tw9910_enable,
+	.platform_disable = camera_disable,
+};
+
 static struct i2c_board_info migor_i2c_devices[] = {
 	{
 		I2C_BOARD_INFO("rs5c372b", 0x32),
@@ -438,51 +466,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,12 +480,9 @@ static struct platform_device *migor_devices[] __initdata = {
 	&smc91x_eth_device,
 	&sh_keysc_device,
 	&migor_lcdc_device,
-	&migor_ceu_device,
 	&migor_nor_flash_device,
 	&migor_nand_flash_device,
 	&sdhi_cn9_device,
-	&migor_camera[0],
-	&migor_camera[1],
 };

 extern char migor_sdram_enter_start;
@@ -505,8 +492,6 @@ extern char migor_sdram_leave_end;

 static int __init migor_devices_setup(void)
 {
-	struct resource *r;
-
 	/* register board specific self-refresh code */
 	sh_mobile_register_self_refresh(SUSP_SH_STANDBY | SUSP_SH_SF,
 					&migor_sdram_enter_start,
@@ -651,16 +636,19 @@ static int __init migor_devices_setup(void)
 	 */
 	__raw_writew(__raw_readw(PORT_MSELCRA) | 1, PORT_MSELCRA);

-	/* Setup additional memory resource for CEU video buffers */
-	r = &migor_ceu_device.resource[2];
-	r->flags = IORESOURCE_MEM;
-	r->start = ceu_dma_membase;
-	r->end = r->start + CEU_BUFFER_MEMORY_SIZE - 1;
-	r->name = "ceu";
-
 	i2c_register_board_info(0, migor_i2c_devices,
 				ARRAY_SIZE(migor_i2c_devices));

+	/* Initialize CEU platform device separately to map memory first */
+	device_initialize(&migor_ceu_device.dev);
+	arch_setup_pdev_archdata(&migor_ceu_device);
+	dma_declare_coherent_memory(&migor_ceu_device.dev,
+				    ceu_dma_membase, ceu_dma_membase,
+				    ceu_dma_membase + CEU_BUFFER_MEMORY_SIZE - 1,
+				    DMA_MEMORY_EXCLUSIVE);
+
+	platform_device_add(&migor_ceu_device);
+
 	return platform_add_devices(migor_devices, ARRAY_SIZE(migor_devices));
 }
 arch_initcall(migor_devices_setup);
--
2.7.4

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

* [PATCH v1 06/10] sh: sh7722: Rename CEU clock
  2017-11-15 10:55 [PATCH v1 00/10] Renesas Capture Engine Unit (CEU) V4L2 driver Jacopo Mondi
                   ` (4 preceding siblings ...)
  2017-11-15 10:55 ` [PATCH v1 05/10] arch: sh: migor: Use new renesas-ceu camera driver Jacopo Mondi
@ 2017-11-15 10:55 ` Jacopo Mondi
  2017-11-15 13:13   ` Geert Uytterhoeven
  2017-11-15 10:56 ` [PATCH v1 07/10] v4l: i2c: Copy ov772x soc_camera sensor driver Jacopo Mondi
                   ` (3 subsequent siblings)
  9 siblings, 1 reply; 56+ messages in thread
From: Jacopo Mondi @ 2017-11-15 10:55 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil
  Cc: Jacopo Mondi, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Rename CEU clock to match the new platform driver name used in Migo-R.

There are no other sh7722 based devices Migo-R apart, so we can safely
rename this.

Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
---
 arch/sh/kernel/cpu/sh4a/clock-sh7722.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/sh/kernel/cpu/sh4a/clock-sh7722.c b/arch/sh/kernel/cpu/sh4a/clock-sh7722.c
index 8f07a1a..d85091e 100644
--- a/arch/sh/kernel/cpu/sh4a/clock-sh7722.c
+++ b/arch/sh/kernel/cpu/sh4a/clock-sh7722.c
@@ -223,7 +223,7 @@ static struct clk_lookup lookups[] = {
 	CLKDEV_DEV_ID("sh-vou.0", &mstp_clks[HWBLK_VOU]),
 	CLKDEV_CON_ID("jpu0", &mstp_clks[HWBLK_JPU]),
 	CLKDEV_CON_ID("beu0", &mstp_clks[HWBLK_BEU]),
-	CLKDEV_DEV_ID("sh_mobile_ceu.0", &mstp_clks[HWBLK_CEU]),
+	CLKDEV_DEV_ID("renesas-ceu.0", &mstp_clks[HWBLK_CEU]),
 	CLKDEV_CON_ID("veu0", &mstp_clks[HWBLK_VEU]),
 	CLKDEV_CON_ID("vpu0", &mstp_clks[HWBLK_VPU]),
 	CLKDEV_DEV_ID("sh_mobile_lcdc_fb.0", &mstp_clks[HWBLK_LCDC]),
-- 
2.7.4

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

* [PATCH v1 07/10] v4l: i2c: Copy ov772x soc_camera sensor driver
  2017-11-15 10:55 [PATCH v1 00/10] Renesas Capture Engine Unit (CEU) V4L2 driver Jacopo Mondi
                   ` (5 preceding siblings ...)
  2017-11-15 10:55 ` [PATCH v1 06/10] sh: sh7722: Rename CEU clock Jacopo Mondi
@ 2017-11-15 10:56 ` Jacopo Mondi
  2017-12-11 14:49   ` Laurent Pinchart
                     ` (2 more replies)
  2017-11-15 10:56 ` [PATCH v1 08/10] media: i2c: ov772x: Remove soc_camera dependencies Jacopo Mondi
                   ` (2 subsequent siblings)
  9 siblings, 3 replies; 56+ messages in thread
From: Jacopo Mondi @ 2017-11-15 10:56 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil
  Cc: Jacopo Mondi, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Copy the soc_camera based driver in v4l2 sensor driver directory.
This commit just copies the original file without modifying it.

Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
---
 drivers/media/i2c/ov772x.c | 1124 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 1124 insertions(+)
 create mode 100644 drivers/media/i2c/ov772x.c

diff --git a/drivers/media/i2c/ov772x.c b/drivers/media/i2c/ov772x.c
new file mode 100644
index 0000000..8063835
--- /dev/null
+++ b/drivers/media/i2c/ov772x.c
@@ -0,0 +1,1124 @@
+/*
+ * ov772x Camera Driver
+ *
+ * Copyright (C) 2008 Renesas Solutions Corp.
+ * Kuninori Morimoto <morimoto.kuninori@renesas.com>
+ *
+ * Based on ov7670 and soc_camera_platform driver,
+ *
+ * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net>
+ * Copyright (C) 2008 Magnus Damm
+ * 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 version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/v4l2-mediabus.h>
+#include <linux/videodev2.h>
+
+#include <media/i2c/ov772x.h>
+#include <media/soc_camera.h>
+#include <media/v4l2-clk.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-image-sizes.h>
+
+/*
+ * register offset
+ */
+#define GAIN        0x00 /* AGC - Gain control gain setting */
+#define BLUE        0x01 /* AWB - Blue channel gain setting */
+#define RED         0x02 /* AWB - Red   channel gain setting */
+#define GREEN       0x03 /* AWB - Green channel gain setting */
+#define COM1        0x04 /* Common control 1 */
+#define BAVG        0x05 /* U/B Average Level */
+#define GAVG        0x06 /* Y/Gb Average Level */
+#define RAVG        0x07 /* V/R Average Level */
+#define AECH        0x08 /* Exposure Value - AEC MSBs */
+#define COM2        0x09 /* Common control 2 */
+#define PID         0x0A /* Product ID Number MSB */
+#define VER         0x0B /* Product ID Number LSB */
+#define COM3        0x0C /* Common control 3 */
+#define COM4        0x0D /* Common control 4 */
+#define COM5        0x0E /* Common control 5 */
+#define COM6        0x0F /* Common control 6 */
+#define AEC         0x10 /* Exposure Value */
+#define CLKRC       0x11 /* Internal clock */
+#define COM7        0x12 /* Common control 7 */
+#define COM8        0x13 /* Common control 8 */
+#define COM9        0x14 /* Common control 9 */
+#define COM10       0x15 /* Common control 10 */
+#define REG16       0x16 /* Register 16 */
+#define HSTART      0x17 /* Horizontal sensor size */
+#define HSIZE       0x18 /* Horizontal frame (HREF column) end high 8-bit */
+#define VSTART      0x19 /* Vertical frame (row) start high 8-bit */
+#define VSIZE       0x1A /* Vertical sensor size */
+#define PSHFT       0x1B /* Data format - pixel delay select */
+#define MIDH        0x1C /* Manufacturer ID byte - high */
+#define MIDL        0x1D /* Manufacturer ID byte - low  */
+#define LAEC        0x1F /* Fine AEC value */
+#define COM11       0x20 /* Common control 11 */
+#define BDBASE      0x22 /* Banding filter Minimum AEC value */
+#define DBSTEP      0x23 /* Banding filter Maximum Setp */
+#define AEW         0x24 /* AGC/AEC - Stable operating region (upper limit) */
+#define AEB         0x25 /* AGC/AEC - Stable operating region (lower limit) */
+#define VPT         0x26 /* AGC/AEC Fast mode operating region */
+#define REG28       0x28 /* Register 28 */
+#define HOUTSIZE    0x29 /* Horizontal data output size MSBs */
+#define EXHCH       0x2A /* Dummy pixel insert MSB */
+#define EXHCL       0x2B /* Dummy pixel insert LSB */
+#define VOUTSIZE    0x2C /* Vertical data output size MSBs */
+#define ADVFL       0x2D /* LSB of insert dummy lines in Vertical direction */
+#define ADVFH       0x2E /* MSG of insert dummy lines in Vertical direction */
+#define YAVE        0x2F /* Y/G Channel Average value */
+#define LUMHTH      0x30 /* Histogram AEC/AGC Luminance high level threshold */
+#define LUMLTH      0x31 /* Histogram AEC/AGC Luminance low  level threshold */
+#define HREF        0x32 /* Image start and size control */
+#define DM_LNL      0x33 /* Dummy line low  8 bits */
+#define DM_LNH      0x34 /* Dummy line high 8 bits */
+#define ADOFF_B     0x35 /* AD offset compensation value for B  channel */
+#define ADOFF_R     0x36 /* AD offset compensation value for R  channel */
+#define ADOFF_GB    0x37 /* AD offset compensation value for Gb channel */
+#define ADOFF_GR    0x38 /* AD offset compensation value for Gr channel */
+#define OFF_B       0x39 /* Analog process B  channel offset value */
+#define OFF_R       0x3A /* Analog process R  channel offset value */
+#define OFF_GB      0x3B /* Analog process Gb channel offset value */
+#define OFF_GR      0x3C /* Analog process Gr channel offset value */
+#define COM12       0x3D /* Common control 12 */
+#define COM13       0x3E /* Common control 13 */
+#define COM14       0x3F /* Common control 14 */
+#define COM15       0x40 /* Common control 15*/
+#define COM16       0x41 /* Common control 16 */
+#define TGT_B       0x42 /* BLC blue channel target value */
+#define TGT_R       0x43 /* BLC red  channel target value */
+#define TGT_GB      0x44 /* BLC Gb   channel target value */
+#define TGT_GR      0x45 /* BLC Gr   channel target value */
+/* for ov7720 */
+#define LCC0        0x46 /* Lens correction control 0 */
+#define LCC1        0x47 /* Lens correction option 1 - X coordinate */
+#define LCC2        0x48 /* Lens correction option 2 - Y coordinate */
+#define LCC3        0x49 /* Lens correction option 3 */
+#define LCC4        0x4A /* Lens correction option 4 - radius of the circular */
+#define LCC5        0x4B /* Lens correction option 5 */
+#define LCC6        0x4C /* Lens correction option 6 */
+/* for ov7725 */
+#define LC_CTR      0x46 /* Lens correction control */
+#define LC_XC       0x47 /* X coordinate of lens correction center relative */
+#define LC_YC       0x48 /* Y coordinate of lens correction center relative */
+#define LC_COEF     0x49 /* Lens correction coefficient */
+#define LC_RADI     0x4A /* Lens correction radius */
+#define LC_COEFB    0x4B /* Lens B channel compensation coefficient */
+#define LC_COEFR    0x4C /* Lens R channel compensation coefficient */
+
+#define FIXGAIN     0x4D /* Analog fix gain amplifer */
+#define AREF0       0x4E /* Sensor reference control */
+#define AREF1       0x4F /* Sensor reference current control */
+#define AREF2       0x50 /* Analog reference control */
+#define AREF3       0x51 /* ADC    reference control */
+#define AREF4       0x52 /* ADC    reference control */
+#define AREF5       0x53 /* ADC    reference control */
+#define AREF6       0x54 /* Analog reference control */
+#define AREF7       0x55 /* Analog reference control */
+#define UFIX        0x60 /* U channel fixed value output */
+#define VFIX        0x61 /* V channel fixed value output */
+#define AWBB_BLK    0x62 /* AWB option for advanced AWB */
+#define AWB_CTRL0   0x63 /* AWB control byte 0 */
+#define DSP_CTRL1   0x64 /* DSP control byte 1 */
+#define DSP_CTRL2   0x65 /* DSP control byte 2 */
+#define DSP_CTRL3   0x66 /* DSP control byte 3 */
+#define DSP_CTRL4   0x67 /* DSP control byte 4 */
+#define AWB_BIAS    0x68 /* AWB BLC level clip */
+#define AWB_CTRL1   0x69 /* AWB control  1 */
+#define AWB_CTRL2   0x6A /* AWB control  2 */
+#define AWB_CTRL3   0x6B /* AWB control  3 */
+#define AWB_CTRL4   0x6C /* AWB control  4 */
+#define AWB_CTRL5   0x6D /* AWB control  5 */
+#define AWB_CTRL6   0x6E /* AWB control  6 */
+#define AWB_CTRL7   0x6F /* AWB control  7 */
+#define AWB_CTRL8   0x70 /* AWB control  8 */
+#define AWB_CTRL9   0x71 /* AWB control  9 */
+#define AWB_CTRL10  0x72 /* AWB control 10 */
+#define AWB_CTRL11  0x73 /* AWB control 11 */
+#define AWB_CTRL12  0x74 /* AWB control 12 */
+#define AWB_CTRL13  0x75 /* AWB control 13 */
+#define AWB_CTRL14  0x76 /* AWB control 14 */
+#define AWB_CTRL15  0x77 /* AWB control 15 */
+#define AWB_CTRL16  0x78 /* AWB control 16 */
+#define AWB_CTRL17  0x79 /* AWB control 17 */
+#define AWB_CTRL18  0x7A /* AWB control 18 */
+#define AWB_CTRL19  0x7B /* AWB control 19 */
+#define AWB_CTRL20  0x7C /* AWB control 20 */
+#define AWB_CTRL21  0x7D /* AWB control 21 */
+#define GAM1        0x7E /* Gamma Curve  1st segment input end point */
+#define GAM2        0x7F /* Gamma Curve  2nd segment input end point */
+#define GAM3        0x80 /* Gamma Curve  3rd segment input end point */
+#define GAM4        0x81 /* Gamma Curve  4th segment input end point */
+#define GAM5        0x82 /* Gamma Curve  5th segment input end point */
+#define GAM6        0x83 /* Gamma Curve  6th segment input end point */
+#define GAM7        0x84 /* Gamma Curve  7th segment input end point */
+#define GAM8        0x85 /* Gamma Curve  8th segment input end point */
+#define GAM9        0x86 /* Gamma Curve  9th segment input end point */
+#define GAM10       0x87 /* Gamma Curve 10th segment input end point */
+#define GAM11       0x88 /* Gamma Curve 11th segment input end point */
+#define GAM12       0x89 /* Gamma Curve 12th segment input end point */
+#define GAM13       0x8A /* Gamma Curve 13th segment input end point */
+#define GAM14       0x8B /* Gamma Curve 14th segment input end point */
+#define GAM15       0x8C /* Gamma Curve 15th segment input end point */
+#define SLOP        0x8D /* Gamma curve highest segment slope */
+#define DNSTH       0x8E /* De-noise threshold */
+#define EDGE_STRNGT 0x8F /* Edge strength  control when manual mode */
+#define EDGE_TRSHLD 0x90 /* Edge threshold control when manual mode */
+#define DNSOFF      0x91 /* Auto De-noise threshold control */
+#define EDGE_UPPER  0x92 /* Edge strength upper limit when Auto mode */
+#define EDGE_LOWER  0x93 /* Edge strength lower limit when Auto mode */
+#define MTX1        0x94 /* Matrix coefficient 1 */
+#define MTX2        0x95 /* Matrix coefficient 2 */
+#define MTX3        0x96 /* Matrix coefficient 3 */
+#define MTX4        0x97 /* Matrix coefficient 4 */
+#define MTX5        0x98 /* Matrix coefficient 5 */
+#define MTX6        0x99 /* Matrix coefficient 6 */
+#define MTX_CTRL    0x9A /* Matrix control */
+#define BRIGHT      0x9B /* Brightness control */
+#define CNTRST      0x9C /* Contrast contrast */
+#define CNTRST_CTRL 0x9D /* Contrast contrast center */
+#define UVAD_J0     0x9E /* Auto UV adjust contrast 0 */
+#define UVAD_J1     0x9F /* Auto UV adjust contrast 1 */
+#define SCAL0       0xA0 /* Scaling control 0 */
+#define SCAL1       0xA1 /* Scaling control 1 */
+#define SCAL2       0xA2 /* Scaling control 2 */
+#define FIFODLYM    0xA3 /* FIFO manual mode delay control */
+#define FIFODLYA    0xA4 /* FIFO auto   mode delay control */
+#define SDE         0xA6 /* Special digital effect control */
+#define USAT        0xA7 /* U component saturation control */
+#define VSAT        0xA8 /* V component saturation control */
+/* for ov7720 */
+#define HUE0        0xA9 /* Hue control 0 */
+#define HUE1        0xAA /* Hue control 1 */
+/* for ov7725 */
+#define HUECOS      0xA9 /* Cosine value */
+#define HUESIN      0xAA /* Sine value */
+
+#define SIGN        0xAB /* Sign bit for Hue and contrast */
+#define DSPAUTO     0xAC /* DSP auto function ON/OFF control */
+
+/*
+ * register detail
+ */
+
+/* COM2 */
+#define SOFT_SLEEP_MODE 0x10	/* Soft sleep mode */
+				/* Output drive capability */
+#define OCAP_1x         0x00	/* 1x */
+#define OCAP_2x         0x01	/* 2x */
+#define OCAP_3x         0x02	/* 3x */
+#define OCAP_4x         0x03	/* 4x */
+
+/* COM3 */
+#define SWAP_MASK       (SWAP_RGB | SWAP_YUV | SWAP_ML)
+#define IMG_MASK        (VFLIP_IMG | HFLIP_IMG)
+
+#define VFLIP_IMG       0x80	/* Vertical flip image ON/OFF selection */
+#define HFLIP_IMG       0x40	/* Horizontal mirror image ON/OFF selection */
+#define SWAP_RGB        0x20	/* Swap B/R  output sequence in RGB mode */
+#define SWAP_YUV        0x10	/* Swap Y/UV output sequence in YUV mode */
+#define SWAP_ML         0x08	/* Swap output MSB/LSB */
+				/* Tri-state option for output clock */
+#define NOTRI_CLOCK     0x04	/*   0: Tri-state    at this period */
+				/*   1: No tri-state at this period */
+				/* Tri-state option for output data */
+#define NOTRI_DATA      0x02	/*   0: Tri-state    at this period */
+				/*   1: No tri-state at this period */
+#define SCOLOR_TEST     0x01	/* Sensor color bar test pattern */
+
+/* COM4 */
+				/* PLL frequency control */
+#define PLL_BYPASS      0x00	/*  00: Bypass PLL */
+#define PLL_4x          0x40	/*  01: PLL 4x */
+#define PLL_6x          0x80	/*  10: PLL 6x */
+#define PLL_8x          0xc0	/*  11: PLL 8x */
+				/* AEC evaluate window */
+#define AEC_FULL        0x00	/*  00: Full window */
+#define AEC_1p2         0x10	/*  01: 1/2  window */
+#define AEC_1p4         0x20	/*  10: 1/4  window */
+#define AEC_2p3         0x30	/*  11: Low 2/3 window */
+
+/* COM5 */
+#define AFR_ON_OFF      0x80	/* Auto frame rate control ON/OFF selection */
+#define AFR_SPPED       0x40	/* Auto frame rate control speed selection */
+				/* Auto frame rate max rate control */
+#define AFR_NO_RATE     0x00	/*     No  reduction of frame rate */
+#define AFR_1p2         0x10	/*     Max reduction to 1/2 frame rate */
+#define AFR_1p4         0x20	/*     Max reduction to 1/4 frame rate */
+#define AFR_1p8         0x30	/* Max reduction to 1/8 frame rate */
+				/* Auto frame rate active point control */
+#define AF_2x           0x00	/*     Add frame when AGC reaches  2x gain */
+#define AF_4x           0x04	/*     Add frame when AGC reaches  4x gain */
+#define AF_8x           0x08	/*     Add frame when AGC reaches  8x gain */
+#define AF_16x          0x0c	/* Add frame when AGC reaches 16x gain */
+				/* AEC max step control */
+#define AEC_NO_LIMIT    0x01	/*   0 : AEC incease step has limit */
+				/*   1 : No limit to AEC increase step */
+
+/* COM7 */
+				/* SCCB Register Reset */
+#define SCCB_RESET      0x80	/*   0 : No change */
+				/*   1 : Resets all registers to default */
+				/* Resolution selection */
+#define SLCT_MASK       0x40	/*   Mask of VGA or QVGA */
+#define SLCT_VGA        0x00	/*   0 : VGA */
+#define SLCT_QVGA       0x40	/*   1 : QVGA */
+#define ITU656_ON_OFF   0x20	/* ITU656 protocol ON/OFF selection */
+#define SENSOR_RAW	0x10	/* Sensor RAW */
+				/* RGB output format control */
+#define FMT_MASK        0x0c	/*      Mask of color format */
+#define FMT_GBR422      0x00	/*      00 : GBR 4:2:2 */
+#define FMT_RGB565      0x04	/*      01 : RGB 565 */
+#define FMT_RGB555      0x08	/*      10 : RGB 555 */
+#define FMT_RGB444      0x0c	/* 11 : RGB 444 */
+				/* Output format control */
+#define OFMT_MASK       0x03    /*      Mask of output format */
+#define OFMT_YUV        0x00	/*      00 : YUV */
+#define OFMT_P_BRAW     0x01	/*      01 : Processed Bayer RAW */
+#define OFMT_RGB        0x02	/*      10 : RGB */
+#define OFMT_BRAW       0x03	/* 11 : Bayer RAW */
+
+/* COM8 */
+#define FAST_ALGO       0x80	/* Enable fast AGC/AEC algorithm */
+				/* AEC Setp size limit */
+#define UNLMT_STEP      0x40	/*   0 : Step size is limited */
+				/*   1 : Unlimited step size */
+#define BNDF_ON_OFF     0x20	/* Banding filter ON/OFF */
+#define AEC_BND         0x10	/* Enable AEC below banding value */
+#define AEC_ON_OFF      0x08	/* Fine AEC ON/OFF control */
+#define AGC_ON          0x04	/* AGC Enable */
+#define AWB_ON          0x02	/* AWB Enable */
+#define AEC_ON          0x01	/* AEC Enable */
+
+/* COM9 */
+#define BASE_AECAGC     0x80	/* Histogram or average based AEC/AGC */
+				/* Automatic gain ceiling - maximum AGC value */
+#define GAIN_2x         0x00	/*    000 :   2x */
+#define GAIN_4x         0x10	/*    001 :   4x */
+#define GAIN_8x         0x20	/*    010 :   8x */
+#define GAIN_16x        0x30	/*    011 :  16x */
+#define GAIN_32x        0x40	/*    100 :  32x */
+#define GAIN_64x        0x50	/* 101 :  64x */
+#define GAIN_128x       0x60	/* 110 : 128x */
+#define DROP_VSYNC      0x04	/* Drop VSYNC output of corrupt frame */
+#define DROP_HREF       0x02	/* Drop HREF  output of corrupt frame */
+
+/* COM11 */
+#define SGLF_ON_OFF     0x02	/* Single frame ON/OFF selection */
+#define SGLF_TRIG       0x01	/* Single frame transfer trigger */
+
+/* HREF */
+#define HREF_VSTART_SHIFT	6	/* VSTART LSB */
+#define HREF_HSTART_SHIFT	4	/* HSTART 2 LSBs */
+#define HREF_VSIZE_SHIFT	2	/* VSIZE LSB */
+#define HREF_HSIZE_SHIFT	0	/* HSIZE 2 LSBs */
+
+/* EXHCH */
+#define EXHCH_VSIZE_SHIFT	2	/* VOUTSIZE LSB */
+#define EXHCH_HSIZE_SHIFT	0	/* HOUTSIZE 2 LSBs */
+
+/* DSP_CTRL1 */
+#define FIFO_ON         0x80	/* FIFO enable/disable selection */
+#define UV_ON_OFF       0x40	/* UV adjust function ON/OFF selection */
+#define YUV444_2_422    0x20	/* YUV444 to 422 UV channel option selection */
+#define CLR_MTRX_ON_OFF 0x10	/* Color matrix ON/OFF selection */
+#define INTPLT_ON_OFF   0x08	/* Interpolation ON/OFF selection */
+#define GMM_ON_OFF      0x04	/* Gamma function ON/OFF selection */
+#define AUTO_BLK_ON_OFF 0x02	/* Black defect auto correction ON/OFF */
+#define AUTO_WHT_ON_OFF 0x01	/* White define auto correction ON/OFF */
+
+/* DSP_CTRL3 */
+#define UV_MASK         0x80	/* UV output sequence option */
+#define UV_ON           0x80	/*   ON */
+#define UV_OFF          0x00	/*   OFF */
+#define CBAR_MASK       0x20	/* DSP Color bar mask */
+#define CBAR_ON         0x20	/*   ON */
+#define CBAR_OFF        0x00	/*   OFF */
+
+/* DSP_CTRL4 */
+#define DSP_OFMT_YUV	0x00
+#define DSP_OFMT_RGB	0x00
+#define DSP_OFMT_RAW8	0x02
+#define DSP_OFMT_RAW10	0x03
+
+/* DSPAUTO (DSP Auto Function ON/OFF Control) */
+#define AWB_ACTRL       0x80 /* AWB auto threshold control */
+#define DENOISE_ACTRL   0x40 /* De-noise auto threshold control */
+#define EDGE_ACTRL      0x20 /* Edge enhancement auto strength control */
+#define UV_ACTRL        0x10 /* UV adjust auto slope control */
+#define SCAL0_ACTRL     0x08 /* Auto scaling factor control */
+#define SCAL1_2_ACTRL   0x04 /* Auto scaling factor control */
+
+#define OV772X_MAX_WIDTH	VGA_WIDTH
+#define OV772X_MAX_HEIGHT	VGA_HEIGHT
+
+/*
+ * ID
+ */
+#define OV7720  0x7720
+#define OV7725  0x7721
+#define VERSION(pid, ver) ((pid<<8)|(ver&0xFF))
+
+/*
+ * struct
+ */
+
+struct ov772x_color_format {
+	u32 code;
+	enum v4l2_colorspace colorspace;
+	u8 dsp3;
+	u8 dsp4;
+	u8 com3;
+	u8 com7;
+};
+
+struct ov772x_win_size {
+	char                     *name;
+	unsigned char             com7_bit;
+	struct v4l2_rect	  rect;
+};
+
+struct ov772x_priv {
+	struct v4l2_subdev                subdev;
+	struct v4l2_ctrl_handler	  hdl;
+	struct v4l2_clk			 *clk;
+	struct ov772x_camera_info        *info;
+	const struct ov772x_color_format *cfmt;
+	const struct ov772x_win_size     *win;
+	unsigned short                    flag_vflip:1;
+	unsigned short                    flag_hflip:1;
+	/* band_filter = COM8[5] ? 256 - BDBASE : 0 */
+	unsigned short                    band_filter;
+};
+
+/*
+ * supported color format list
+ */
+static const struct ov772x_color_format ov772x_cfmts[] = {
+	{
+		.code		= MEDIA_BUS_FMT_YUYV8_2X8,
+		.colorspace	= V4L2_COLORSPACE_JPEG,
+		.dsp3		= 0x0,
+		.dsp4		= DSP_OFMT_YUV,
+		.com3		= SWAP_YUV,
+		.com7		= OFMT_YUV,
+	},
+	{
+		.code		= MEDIA_BUS_FMT_YVYU8_2X8,
+		.colorspace	= V4L2_COLORSPACE_JPEG,
+		.dsp3		= UV_ON,
+		.dsp4		= DSP_OFMT_YUV,
+		.com3		= SWAP_YUV,
+		.com7		= OFMT_YUV,
+	},
+	{
+		.code		= MEDIA_BUS_FMT_UYVY8_2X8,
+		.colorspace	= V4L2_COLORSPACE_JPEG,
+		.dsp3		= 0x0,
+		.dsp4		= DSP_OFMT_YUV,
+		.com3		= 0x0,
+		.com7		= OFMT_YUV,
+	},
+	{
+		.code		= MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE,
+		.colorspace	= V4L2_COLORSPACE_SRGB,
+		.dsp3		= 0x0,
+		.dsp4		= DSP_OFMT_YUV,
+		.com3		= SWAP_RGB,
+		.com7		= FMT_RGB555 | OFMT_RGB,
+	},
+	{
+		.code		= MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE,
+		.colorspace	= V4L2_COLORSPACE_SRGB,
+		.dsp3		= 0x0,
+		.dsp4		= DSP_OFMT_YUV,
+		.com3		= 0x0,
+		.com7		= FMT_RGB555 | OFMT_RGB,
+	},
+	{
+		.code		= MEDIA_BUS_FMT_RGB565_2X8_LE,
+		.colorspace	= V4L2_COLORSPACE_SRGB,
+		.dsp3		= 0x0,
+		.dsp4		= DSP_OFMT_YUV,
+		.com3		= SWAP_RGB,
+		.com7		= FMT_RGB565 | OFMT_RGB,
+	},
+	{
+		.code		= MEDIA_BUS_FMT_RGB565_2X8_BE,
+		.colorspace	= V4L2_COLORSPACE_SRGB,
+		.dsp3		= 0x0,
+		.dsp4		= DSP_OFMT_YUV,
+		.com3		= 0x0,
+		.com7		= FMT_RGB565 | OFMT_RGB,
+	},
+	{
+		/* Setting DSP4 to DSP_OFMT_RAW8 still gives 10-bit output,
+		 * regardless of the COM7 value. We can thus only support 10-bit
+		 * Bayer until someone figures it out.
+		 */
+		.code		= MEDIA_BUS_FMT_SBGGR10_1X10,
+		.colorspace	= V4L2_COLORSPACE_SRGB,
+		.dsp3		= 0x0,
+		.dsp4		= DSP_OFMT_RAW10,
+		.com3		= 0x0,
+		.com7		= SENSOR_RAW | OFMT_BRAW,
+	},
+};
+
+
+/*
+ * window size list
+ */
+
+static const struct ov772x_win_size ov772x_win_sizes[] = {
+	{
+		.name     = "VGA",
+		.com7_bit = SLCT_VGA,
+		.rect = {
+			.left = 140,
+			.top = 14,
+			.width = VGA_WIDTH,
+			.height = VGA_HEIGHT,
+		},
+	}, {
+		.name     = "QVGA",
+		.com7_bit = SLCT_QVGA,
+		.rect = {
+			.left = 252,
+			.top = 6,
+			.width = QVGA_WIDTH,
+			.height = QVGA_HEIGHT,
+		},
+	},
+};
+
+/*
+ * general function
+ */
+
+static struct ov772x_priv *to_ov772x(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct ov772x_priv, subdev);
+}
+
+static inline int ov772x_read(struct i2c_client *client, u8 addr)
+{
+	return i2c_smbus_read_byte_data(client, addr);
+}
+
+static inline int ov772x_write(struct i2c_client *client, u8 addr, u8 value)
+{
+	return i2c_smbus_write_byte_data(client, addr, value);
+}
+
+static int ov772x_mask_set(struct i2c_client *client, u8  command, u8  mask,
+			   u8  set)
+{
+	s32 val = ov772x_read(client, command);
+	if (val < 0)
+		return val;
+
+	val &= ~mask;
+	val |= set & mask;
+
+	return ov772x_write(client, command, val);
+}
+
+static int ov772x_reset(struct i2c_client *client)
+{
+	int ret;
+
+	ret = ov772x_write(client, COM7, SCCB_RESET);
+	if (ret < 0)
+		return ret;
+
+	msleep(1);
+
+	return ov772x_mask_set(client, COM2, SOFT_SLEEP_MODE, SOFT_SLEEP_MODE);
+}
+
+/*
+ * soc_camera_ops function
+ */
+
+static int ov772x_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct ov772x_priv *priv = to_ov772x(sd);
+
+	if (!enable) {
+		ov772x_mask_set(client, COM2, SOFT_SLEEP_MODE, SOFT_SLEEP_MODE);
+		return 0;
+	}
+
+	ov772x_mask_set(client, COM2, SOFT_SLEEP_MODE, 0);
+
+	dev_dbg(&client->dev, "format %d, win %s\n",
+		priv->cfmt->code, priv->win->name);
+
+	return 0;
+}
+
+static int ov772x_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct ov772x_priv *priv = container_of(ctrl->handler,
+						struct ov772x_priv, hdl);
+	struct v4l2_subdev *sd = &priv->subdev;
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	int ret = 0;
+	u8 val;
+
+	switch (ctrl->id) {
+	case V4L2_CID_VFLIP:
+		val = ctrl->val ? VFLIP_IMG : 0x00;
+		priv->flag_vflip = ctrl->val;
+		if (priv->info->flags & OV772X_FLAG_VFLIP)
+			val ^= VFLIP_IMG;
+		return ov772x_mask_set(client, COM3, VFLIP_IMG, val);
+	case V4L2_CID_HFLIP:
+		val = ctrl->val ? HFLIP_IMG : 0x00;
+		priv->flag_hflip = ctrl->val;
+		if (priv->info->flags & OV772X_FLAG_HFLIP)
+			val ^= HFLIP_IMG;
+		return ov772x_mask_set(client, COM3, HFLIP_IMG, val);
+	case V4L2_CID_BAND_STOP_FILTER:
+		if (!ctrl->val) {
+			/* Switch the filter off, it is on now */
+			ret = ov772x_mask_set(client, BDBASE, 0xff, 0xff);
+			if (!ret)
+				ret = ov772x_mask_set(client, COM8,
+						      BNDF_ON_OFF, 0);
+		} else {
+			/* Switch the filter on, set AEC low limit */
+			val = 256 - ctrl->val;
+			ret = ov772x_mask_set(client, COM8,
+					      BNDF_ON_OFF, BNDF_ON_OFF);
+			if (!ret)
+				ret = ov772x_mask_set(client, BDBASE,
+						      0xff, val);
+		}
+		if (!ret)
+			priv->band_filter = ctrl->val;
+		return ret;
+	}
+
+	return -EINVAL;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int ov772x_g_register(struct v4l2_subdev *sd,
+			     struct v4l2_dbg_register *reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	int ret;
+
+	reg->size = 1;
+	if (reg->reg > 0xff)
+		return -EINVAL;
+
+	ret = ov772x_read(client, reg->reg);
+	if (ret < 0)
+		return ret;
+
+	reg->val = (__u64)ret;
+
+	return 0;
+}
+
+static int ov772x_s_register(struct v4l2_subdev *sd,
+			     const struct v4l2_dbg_register *reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	if (reg->reg > 0xff ||
+	    reg->val > 0xff)
+		return -EINVAL;
+
+	return ov772x_write(client, reg->reg, reg->val);
+}
+#endif
+
+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);
+}
+
+static const struct ov772x_win_size *ov772x_select_win(u32 width, u32 height)
+{
+	const struct ov772x_win_size *win = &ov772x_win_sizes[0];
+	u32 best_diff = UINT_MAX;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(ov772x_win_sizes); ++i) {
+		u32 diff = abs(width - ov772x_win_sizes[i].rect.width)
+			 + abs(height - ov772x_win_sizes[i].rect.height);
+		if (diff < best_diff) {
+			best_diff = diff;
+			win = &ov772x_win_sizes[i];
+		}
+	}
+
+	return win;
+}
+
+static void ov772x_select_params(const struct v4l2_mbus_framefmt *mf,
+				 const struct ov772x_color_format **cfmt,
+				 const struct ov772x_win_size **win)
+{
+	unsigned int i;
+
+	/* Select a format. */
+	*cfmt = &ov772x_cfmts[0];
+
+	for (i = 0; i < ARRAY_SIZE(ov772x_cfmts); i++) {
+		if (mf->code == ov772x_cfmts[i].code) {
+			*cfmt = &ov772x_cfmts[i];
+			break;
+		}
+	}
+
+	/* Select a window size. */
+	*win = ov772x_select_win(mf->width, mf->height);
+}
+
+static int ov772x_set_params(struct ov772x_priv *priv,
+			     const struct ov772x_color_format *cfmt,
+			     const struct ov772x_win_size *win)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&priv->subdev);
+	int ret;
+	u8  val;
+
+	/*
+	 * reset hardware
+	 */
+	ov772x_reset(client);
+
+	/*
+	 * Edge Ctrl
+	 */
+	if (priv->info->edgectrl.strength & OV772X_MANUAL_EDGE_CTRL) {
+
+		/*
+		 * Manual Edge Control Mode
+		 *
+		 * Edge auto strength bit is set by default.
+		 * Remove it when manual mode.
+		 */
+
+		ret = ov772x_mask_set(client, DSPAUTO, EDGE_ACTRL, 0x00);
+		if (ret < 0)
+			goto ov772x_set_fmt_error;
+
+		ret = ov772x_mask_set(client,
+				      EDGE_TRSHLD, OV772X_EDGE_THRESHOLD_MASK,
+				      priv->info->edgectrl.threshold);
+		if (ret < 0)
+			goto ov772x_set_fmt_error;
+
+		ret = ov772x_mask_set(client,
+				      EDGE_STRNGT, OV772X_EDGE_STRENGTH_MASK,
+				      priv->info->edgectrl.strength);
+		if (ret < 0)
+			goto ov772x_set_fmt_error;
+
+	} else if (priv->info->edgectrl.upper > priv->info->edgectrl.lower) {
+		/*
+		 * Auto Edge Control Mode
+		 *
+		 * set upper and lower limit
+		 */
+		ret = ov772x_mask_set(client,
+				      EDGE_UPPER, OV772X_EDGE_UPPER_MASK,
+				      priv->info->edgectrl.upper);
+		if (ret < 0)
+			goto ov772x_set_fmt_error;
+
+		ret = ov772x_mask_set(client,
+				      EDGE_LOWER, OV772X_EDGE_LOWER_MASK,
+				      priv->info->edgectrl.lower);
+		if (ret < 0)
+			goto ov772x_set_fmt_error;
+	}
+
+	/* Format and window size */
+	ret = ov772x_write(client, HSTART, win->rect.left >> 2);
+	if (ret < 0)
+		goto ov772x_set_fmt_error;
+	ret = ov772x_write(client, HSIZE, win->rect.width >> 2);
+	if (ret < 0)
+		goto ov772x_set_fmt_error;
+	ret = ov772x_write(client, VSTART, win->rect.top >> 1);
+	if (ret < 0)
+		goto ov772x_set_fmt_error;
+	ret = ov772x_write(client, VSIZE, win->rect.height >> 1);
+	if (ret < 0)
+		goto ov772x_set_fmt_error;
+	ret = ov772x_write(client, HOUTSIZE, win->rect.width >> 2);
+	if (ret < 0)
+		goto ov772x_set_fmt_error;
+	ret = ov772x_write(client, VOUTSIZE, win->rect.height >> 1);
+	if (ret < 0)
+		goto ov772x_set_fmt_error;
+	ret = ov772x_write(client, HREF,
+			   ((win->rect.top & 1) << HREF_VSTART_SHIFT) |
+			   ((win->rect.left & 3) << HREF_HSTART_SHIFT) |
+			   ((win->rect.height & 1) << HREF_VSIZE_SHIFT) |
+			   ((win->rect.width & 3) << HREF_HSIZE_SHIFT));
+	if (ret < 0)
+		goto ov772x_set_fmt_error;
+	ret = ov772x_write(client, EXHCH,
+			   ((win->rect.height & 1) << EXHCH_VSIZE_SHIFT) |
+			   ((win->rect.width & 3) << EXHCH_HSIZE_SHIFT));
+	if (ret < 0)
+		goto ov772x_set_fmt_error;
+
+	/*
+	 * set DSP_CTRL3
+	 */
+	val = cfmt->dsp3;
+	if (val) {
+		ret = ov772x_mask_set(client,
+				      DSP_CTRL3, UV_MASK, val);
+		if (ret < 0)
+			goto ov772x_set_fmt_error;
+	}
+
+	/* DSP_CTRL4: AEC reference point and DSP output format. */
+	if (cfmt->dsp4) {
+		ret = ov772x_write(client, DSP_CTRL4, cfmt->dsp4);
+		if (ret < 0)
+			goto ov772x_set_fmt_error;
+	}
+
+	/*
+	 * set COM3
+	 */
+	val = cfmt->com3;
+	if (priv->info->flags & OV772X_FLAG_VFLIP)
+		val |= VFLIP_IMG;
+	if (priv->info->flags & OV772X_FLAG_HFLIP)
+		val |= HFLIP_IMG;
+	if (priv->flag_vflip)
+		val ^= VFLIP_IMG;
+	if (priv->flag_hflip)
+		val ^= HFLIP_IMG;
+
+	ret = ov772x_mask_set(client,
+			      COM3, SWAP_MASK | IMG_MASK, val);
+	if (ret < 0)
+		goto ov772x_set_fmt_error;
+
+	/* COM7: Sensor resolution and output format control. */
+	ret = ov772x_write(client, COM7, win->com7_bit | cfmt->com7);
+	if (ret < 0)
+		goto ov772x_set_fmt_error;
+
+	/*
+	 * set COM8
+	 */
+	if (priv->band_filter) {
+		ret = ov772x_mask_set(client, COM8, BNDF_ON_OFF, 1);
+		if (!ret)
+			ret = ov772x_mask_set(client, BDBASE,
+					      0xff, 256 - priv->band_filter);
+		if (ret < 0)
+			goto ov772x_set_fmt_error;
+	}
+
+	return ret;
+
+ov772x_set_fmt_error:
+
+	ov772x_reset(client);
+
+	return ret;
+}
+
+static int ov772x_get_selection(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_selection *sel)
+{
+	if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE)
+		return -EINVAL;
+
+	sel->r.left = 0;
+	sel->r.top = 0;
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+		sel->r.width = OV772X_MAX_WIDTH;
+		sel->r.height = OV772X_MAX_HEIGHT;
+		return 0;
+	case V4L2_SEL_TGT_CROP:
+		sel->r.width = VGA_WIDTH;
+		sel->r.height = VGA_HEIGHT;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ov772x_get_fmt(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *mf = &format->format;
+	struct ov772x_priv *priv = to_ov772x(sd);
+
+	if (format->pad)
+		return -EINVAL;
+
+	mf->width	= priv->win->rect.width;
+	mf->height	= priv->win->rect.height;
+	mf->code	= priv->cfmt->code;
+	mf->colorspace	= priv->cfmt->colorspace;
+	mf->field	= V4L2_FIELD_NONE;
+
+	return 0;
+}
+
+static int ov772x_set_fmt(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_format *format)
+{
+	struct ov772x_priv *priv = to_ov772x(sd);
+	struct v4l2_mbus_framefmt *mf = &format->format;
+	const struct ov772x_color_format *cfmt;
+	const struct ov772x_win_size *win;
+	int ret;
+
+	if (format->pad)
+		return -EINVAL;
+
+	ov772x_select_params(mf, &cfmt, &win);
+
+	mf->code = cfmt->code;
+	mf->width = win->rect.width;
+	mf->height = win->rect.height;
+	mf->field = V4L2_FIELD_NONE;
+	mf->colorspace = cfmt->colorspace;
+
+	if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
+		cfg->try_fmt = *mf;
+		return 0;
+	}
+
+	ret = ov772x_set_params(priv, cfmt, win);
+	if (ret < 0)
+		return ret;
+
+	priv->win = win;
+	priv->cfmt = cfmt;
+	return 0;
+}
+
+static int ov772x_video_probe(struct ov772x_priv *priv)
+{
+	struct i2c_client  *client = v4l2_get_subdevdata(&priv->subdev);
+	u8                  pid, ver;
+	const char         *devname;
+	int		    ret;
+
+	ret = ov772x_s_power(&priv->subdev, 1);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * check and show product ID and manufacturer ID
+	 */
+	pid = ov772x_read(client, PID);
+	ver = ov772x_read(client, VER);
+
+	switch (VERSION(pid, ver)) {
+	case OV7720:
+		devname     = "ov7720";
+		break;
+	case OV7725:
+		devname     = "ov7725";
+		break;
+	default:
+		dev_err(&client->dev,
+			"Product ID error %x:%x\n", pid, ver);
+		ret = -ENODEV;
+		goto done;
+	}
+
+	dev_info(&client->dev,
+		 "%s Product ID %0x:%0x Manufacturer ID %x:%x\n",
+		 devname,
+		 pid,
+		 ver,
+		 ov772x_read(client, MIDH),
+		 ov772x_read(client, MIDL));
+	ret = v4l2_ctrl_handler_setup(&priv->hdl);
+
+done:
+	ov772x_s_power(&priv->subdev, 0);
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops ov772x_ctrl_ops = {
+	.s_ctrl = ov772x_s_ctrl,
+};
+
+static const struct v4l2_subdev_core_ops ov772x_subdev_core_ops = {
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.g_register	= ov772x_g_register,
+	.s_register	= ov772x_s_register,
+#endif
+	.s_power	= ov772x_s_power,
+};
+
+static int ov772x_enum_mbus_code(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_mbus_code_enum *code)
+{
+	if (code->pad || code->index >= ARRAY_SIZE(ov772x_cfmts))
+		return -EINVAL;
+
+	code->code = ov772x_cfmts[code->index].code;
+	return 0;
+}
+
+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;
+}
+
+static const struct v4l2_subdev_video_ops ov772x_subdev_video_ops = {
+	.s_stream	= ov772x_s_stream,
+	.g_mbus_config	= ov772x_g_mbus_config,
+};
+
+static const struct v4l2_subdev_pad_ops ov772x_subdev_pad_ops = {
+	.enum_mbus_code = ov772x_enum_mbus_code,
+	.get_selection	= ov772x_get_selection,
+	.get_fmt	= ov772x_get_fmt,
+	.set_fmt	= ov772x_set_fmt,
+};
+
+static const struct v4l2_subdev_ops ov772x_subdev_ops = {
+	.core	= &ov772x_subdev_core_ops,
+	.video	= &ov772x_subdev_video_ops,
+	.pad	= &ov772x_subdev_pad_ops,
+};
+
+/*
+ * i2c_driver function
+ */
+
+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);
+	int			ret;
+
+	if (!ssdd || !ssdd->drv_priv) {
+		dev_err(&client->dev, "OV772X: missing platform data!\n");
+		return -EINVAL;
+	}
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
+					      I2C_FUNC_PROTOCOL_MANGLING)) {
+		dev_err(&adapter->dev,
+			"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)
+		return -ENOMEM;
+
+	priv->info = ssdd->drv_priv;
+
+	v4l2_i2c_subdev_init(&priv->subdev, client, &ov772x_subdev_ops);
+	v4l2_ctrl_handler_init(&priv->hdl, 3);
+	v4l2_ctrl_new_std(&priv->hdl, &ov772x_ctrl_ops,
+			V4L2_CID_VFLIP, 0, 1, 1, 0);
+	v4l2_ctrl_new_std(&priv->hdl, &ov772x_ctrl_ops,
+			V4L2_CID_HFLIP, 0, 1, 1, 0);
+	v4l2_ctrl_new_std(&priv->hdl, &ov772x_ctrl_ops,
+			V4L2_CID_BAND_STOP_FILTER, 0, 256, 1, 0);
+	priv->subdev.ctrl_handler = &priv->hdl;
+	if (priv->hdl.error)
+		return priv->hdl.error;
+
+	priv->clk = v4l2_clk_get(&client->dev, "mclk");
+	if (IS_ERR(priv->clk)) {
+		ret = PTR_ERR(priv->clk);
+		goto eclkget;
+	}
+
+	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];
+	}
+
+	return ret;
+}
+
+static int ov772x_remove(struct i2c_client *client)
+{
+	struct ov772x_priv *priv = to_ov772x(i2c_get_clientdata(client));
+
+	v4l2_clk_put(priv->clk);
+	v4l2_device_unregister_subdev(&priv->subdev);
+	v4l2_ctrl_handler_free(&priv->hdl);
+	return 0;
+}
+
+static const struct i2c_device_id ov772x_id[] = {
+	{ "ov772x", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ov772x_id);
+
+static struct i2c_driver ov772x_i2c_driver = {
+	.driver = {
+		.name = "ov772x",
+	},
+	.probe    = ov772x_probe,
+	.remove   = ov772x_remove,
+	.id_table = ov772x_id,
+};
+
+module_i2c_driver(ov772x_i2c_driver);
+
+MODULE_DESCRIPTION("SoC Camera driver for ov772x");
+MODULE_AUTHOR("Kuninori Morimoto");
+MODULE_LICENSE("GPL v2");
-- 
2.7.4

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

* [PATCH v1 08/10] media: i2c: ov772x: Remove soc_camera dependencies
  2017-11-15 10:55 [PATCH v1 00/10] Renesas Capture Engine Unit (CEU) V4L2 driver Jacopo Mondi
                   ` (6 preceding siblings ...)
  2017-11-15 10:56 ` [PATCH v1 07/10] v4l: i2c: Copy ov772x soc_camera sensor driver Jacopo Mondi
@ 2017-11-15 10:56 ` Jacopo Mondi
  2017-11-17  0:43   ` Sakari Ailus
  2017-12-11 14:47   ` Laurent Pinchart
  2017-11-15 10:56 ` [PATCH v1 09/10] v4l: i2c: Copy tw9910 soc_camera sensor driver Jacopo Mondi
  2017-11-15 10:56 ` [PATCH v1 10/10] media: i2c: tw9910: Remove soc_camera dependencies Jacopo Mondi
  9 siblings, 2 replies; 56+ messages in thread
From: Jacopo Mondi @ 2017-11-15 10:56 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil
  Cc: Jacopo Mondi, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Remove soc_camera framework dependencies from ov772x sensor driver.
- Handle clock directly
- Register async subdevice
- Add platform specific enable/disable functions
- Adjust build system

This commit does not remove the original soc_camera based driver.

Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
---
 drivers/media/i2c/Kconfig  | 12 +++++++
 drivers/media/i2c/Makefile |  1 +
 drivers/media/i2c/ov772x.c | 88 +++++++++++++++++++++++++++++++---------------
 include/media/i2c/ov772x.h |  3 ++
 4 files changed, 76 insertions(+), 28 deletions(-)

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 9415389..ff251ce 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -629,6 +629,18 @@ config VIDEO_OV5670
 	  To compile this driver as a module, choose M here: the
 	  module will be called ov5670.

+config VIDEO_OV772X
+	tristate "OmniVision OV772x sensor support"
+	depends on I2C && VIDEO_V4L2
+	depends on MEDIA_CAMERA_SUPPORT
+	---help---
+	  This is a Video4Linux2 sensor-level driver for the OmniVision
+	  OV772x camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ov772x.
+
+
 config VIDEO_OV7640
 	tristate "OmniVision OV7640 sensor support"
 	depends on I2C && VIDEO_V4L2
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index f104650..b2459a1 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -66,6 +66,7 @@ obj-$(CONFIG_VIDEO_OV5645) += ov5645.o
 obj-$(CONFIG_VIDEO_OV5647) += ov5647.o
 obj-$(CONFIG_VIDEO_OV5670) += ov5670.o
 obj-$(CONFIG_VIDEO_OV6650) += ov6650.o
+obj-$(CONFIG_VIDEO_OV772X) += ov772x.o
 obj-$(CONFIG_VIDEO_OV7640) += ov7640.o
 obj-$(CONFIG_VIDEO_OV7670) += ov7670.o
 obj-$(CONFIG_VIDEO_OV9650) += ov9650.o
diff --git a/drivers/media/i2c/ov772x.c b/drivers/media/i2c/ov772x.c
index 8063835..9be7e4e 100644
--- a/drivers/media/i2c/ov772x.c
+++ b/drivers/media/i2c/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)
@@ -1000,14 +1024,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;
 }
@@ -1038,12 +1058,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;
 	}

@@ -1059,7 +1078,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);
@@ -1073,21 +1092,33 @@ 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;
 }
@@ -1096,7 +1127,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] 56+ messages in thread

* [PATCH v1 09/10] v4l: i2c: Copy tw9910 soc_camera sensor driver
  2017-11-15 10:55 [PATCH v1 00/10] Renesas Capture Engine Unit (CEU) V4L2 driver Jacopo Mondi
                   ` (7 preceding siblings ...)
  2017-11-15 10:56 ` [PATCH v1 08/10] media: i2c: ov772x: Remove soc_camera dependencies Jacopo Mondi
@ 2017-11-15 10:56 ` Jacopo Mondi
  2017-12-11 14:50   ` Laurent Pinchart
  2017-11-15 10:56 ` [PATCH v1 10/10] media: i2c: tw9910: Remove soc_camera dependencies Jacopo Mondi
  9 siblings, 1 reply; 56+ messages in thread
From: Jacopo Mondi @ 2017-11-15 10:56 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil
  Cc: Jacopo Mondi, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Copy the soc_camera based driver in v4l2 sensor driver directory.
This commit just copies the original file without modifying it.

Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
---
 drivers/media/i2c/tw9910.c | 999 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 999 insertions(+)
 create mode 100644 drivers/media/i2c/tw9910.c

diff --git a/drivers/media/i2c/tw9910.c b/drivers/media/i2c/tw9910.c
new file mode 100644
index 0000000..bdb5e0a
--- /dev/null
+++ b/drivers/media/i2c/tw9910.c
@@ -0,0 +1,999 @@
+/*
+ * tw9910 Video Driver
+ *
+ * Copyright (C) 2008 Renesas Solutions Corp.
+ * Kuninori Morimoto <morimoto.kuninori@renesas.com>
+ *
+ * Based on ov772x driver,
+ *
+ * Copyright (C) 2008 Kuninori Morimoto <morimoto.kuninori@renesas.com>
+ * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net>
+ * Copyright (C) 2008 Magnus Damm
+ * 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 version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#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)
+#define GET_REV(val) (val & 0x07)
+
+/*
+ * register offset
+ */
+#define ID		0x00 /* Product ID Code Register */
+#define STATUS1		0x01 /* Chip Status Register I */
+#define INFORM		0x02 /* Input Format */
+#define OPFORM		0x03 /* Output Format Control Register */
+#define DLYCTR		0x04 /* Hysteresis and HSYNC Delay Control */
+#define OUTCTR1		0x05 /* Output Control I */
+#define ACNTL1		0x06 /* Analog Control Register 1 */
+#define CROP_HI		0x07 /* Cropping Register, High */
+#define VDELAY_LO	0x08 /* Vertical Delay Register, Low */
+#define VACTIVE_LO	0x09 /* Vertical Active Register, Low */
+#define HDELAY_LO	0x0A /* Horizontal Delay Register, Low */
+#define HACTIVE_LO	0x0B /* Horizontal Active Register, Low */
+#define CNTRL1		0x0C /* Control Register I */
+#define VSCALE_LO	0x0D /* Vertical Scaling Register, Low */
+#define SCALE_HI	0x0E /* Scaling Register, High */
+#define HSCALE_LO	0x0F /* Horizontal Scaling Register, Low */
+#define BRIGHT		0x10 /* BRIGHTNESS Control Register */
+#define CONTRAST	0x11 /* CONTRAST Control Register */
+#define SHARPNESS	0x12 /* SHARPNESS Control Register I */
+#define SAT_U		0x13 /* Chroma (U) Gain Register */
+#define SAT_V		0x14 /* Chroma (V) Gain Register */
+#define HUE		0x15 /* Hue Control Register */
+#define CORING1		0x17
+#define CORING2		0x18 /* Coring and IF compensation */
+#define VBICNTL		0x19 /* VBI Control Register */
+#define ACNTL2		0x1A /* Analog Control 2 */
+#define OUTCTR2		0x1B /* Output Control 2 */
+#define SDT		0x1C /* Standard Selection */
+#define SDTR		0x1D /* Standard Recognition */
+#define TEST		0x1F /* Test Control Register */
+#define CLMPG		0x20 /* Clamping Gain */
+#define IAGC		0x21 /* Individual AGC Gain */
+#define AGCGAIN		0x22 /* AGC Gain */
+#define PEAKWT		0x23 /* White Peak Threshold */
+#define CLMPL		0x24 /* Clamp level */
+#define SYNCT		0x25 /* Sync Amplitude */
+#define MISSCNT		0x26 /* Sync Miss Count Register */
+#define PCLAMP		0x27 /* Clamp Position Register */
+#define VCNTL1		0x28 /* Vertical Control I */
+#define VCNTL2		0x29 /* Vertical Control II */
+#define CKILL		0x2A /* Color Killer Level Control */
+#define COMB		0x2B /* Comb Filter Control */
+#define LDLY		0x2C /* Luma Delay and H Filter Control */
+#define MISC1		0x2D /* Miscellaneous Control I */
+#define LOOP		0x2E /* LOOP Control Register */
+#define MISC2		0x2F /* Miscellaneous Control II */
+#define MVSN		0x30 /* Macrovision Detection */
+#define STATUS2		0x31 /* Chip STATUS II */
+#define HFREF		0x32 /* H monitor */
+#define CLMD		0x33 /* CLAMP MODE */
+#define IDCNTL		0x34 /* ID Detection Control */
+#define CLCNTL1		0x35 /* Clamp Control I */
+#define ANAPLLCTL	0x4C
+#define VBIMIN		0x4D
+#define HSLOWCTL	0x4E
+#define WSS3		0x4F
+#define FILLDATA	0x50
+#define SDID		0x51
+#define DID		0x52
+#define WSS1		0x53
+#define WSS2		0x54
+#define VVBI		0x55
+#define LCTL6		0x56
+#define LCTL7		0x57
+#define LCTL8		0x58
+#define LCTL9		0x59
+#define LCTL10		0x5A
+#define LCTL11		0x5B
+#define LCTL12		0x5C
+#define LCTL13		0x5D
+#define LCTL14		0x5E
+#define LCTL15		0x5F
+#define LCTL16		0x60
+#define LCTL17		0x61
+#define LCTL18		0x62
+#define LCTL19		0x63
+#define LCTL20		0x64
+#define LCTL21		0x65
+#define LCTL22		0x66
+#define LCTL23		0x67
+#define LCTL24		0x68
+#define LCTL25		0x69
+#define LCTL26		0x6A
+#define HSBEGIN		0x6B
+#define HSEND		0x6C
+#define OVSDLY		0x6D
+#define OVSEND		0x6E
+#define VBIDELAY	0x6F
+
+/*
+ * register detail
+ */
+
+/* INFORM */
+#define FC27_ON     0x40 /* 1 : Input crystal clock frequency is 27MHz */
+#define FC27_FF     0x00 /* 0 : Square pixel mode. */
+			 /*     Must use 24.54MHz for 60Hz field rate */
+			 /*     source or 29.5MHz for 50Hz field rate */
+#define IFSEL_S     0x10 /* 01 : S-video decoding */
+#define IFSEL_C     0x00 /* 00 : Composite video decoding */
+			 /* Y input video selection */
+#define YSEL_M0     0x00 /*  00 : Mux0 selected */
+#define YSEL_M1     0x04 /*  01 : Mux1 selected */
+#define YSEL_M2     0x08 /*  10 : Mux2 selected */
+#define YSEL_M3     0x10 /*  11 : Mux3 selected */
+
+/* OPFORM */
+#define MODE        0x80 /* 0 : CCIR601 compatible YCrCb 4:2:2 format */
+			 /* 1 : ITU-R-656 compatible data sequence format */
+#define LEN         0x40 /* 0 : 8-bit YCrCb 4:2:2 output format */
+			 /* 1 : 16-bit YCrCb 4:2:2 output format.*/
+#define LLCMODE     0x20 /* 1 : LLC output mode. */
+			 /* 0 : free-run output mode */
+#define AINC        0x10 /* Serial interface auto-indexing control */
+			 /* 0 : auto-increment */
+			 /* 1 : non-auto */
+#define VSCTL       0x08 /* 1 : Vertical out ctrl by DVALID */
+			 /* 0 : Vertical out ctrl by HACTIVE and DVALID */
+#define OEN_TRI_SEL_MASK	0x07
+#define OEN_TRI_SEL_ALL_ON	0x00 /* Enable output for Rev0/Rev1 */
+#define OEN_TRI_SEL_ALL_OFF_r0	0x06 /* All tri-stated for Rev0 */
+#define OEN_TRI_SEL_ALL_OFF_r1	0x07 /* All tri-stated for Rev1 */
+
+/* OUTCTR1 */
+#define VSP_LO      0x00 /* 0 : VS pin output polarity is active low */
+#define VSP_HI      0x80 /* 1 : VS pin output polarity is active high. */
+			 /* VS pin output control */
+#define VSSL_VSYNC  0x00 /*   0 : VSYNC  */
+#define VSSL_VACT   0x10 /*   1 : VACT   */
+#define VSSL_FIELD  0x20 /*   2 : FIELD  */
+#define VSSL_VVALID 0x30 /*   3 : VVALID */
+#define VSSL_ZERO   0x70 /*   7 : 0      */
+#define HSP_LOW     0x00 /* 0 : HS pin output polarity is active low */
+#define HSP_HI      0x08 /* 1 : HS pin output polarity is active high.*/
+			 /* HS pin output control */
+#define HSSL_HACT   0x00 /*   0 : HACT   */
+#define HSSL_HSYNC  0x01 /*   1 : HSYNC  */
+#define HSSL_DVALID 0x02 /*   2 : DVALID */
+#define HSSL_HLOCK  0x03 /*   3 : HLOCK  */
+#define HSSL_ASYNCW 0x04 /*   4 : ASYNCW */
+#define HSSL_ZERO   0x07 /*   7 : 0      */
+
+/* ACNTL1 */
+#define SRESET      0x80 /* resets the device to its default state
+			  * but all register content remain unchanged.
+			  * This bit is self-resetting.
+			  */
+#define ACNTL1_PDN_MASK	0x0e
+#define CLK_PDN		0x08 /* system clock power down */
+#define Y_PDN		0x04 /* Luma ADC power down */
+#define C_PDN		0x02 /* Chroma ADC power down */
+
+/* ACNTL2 */
+#define ACNTL2_PDN_MASK	0x40
+#define PLL_PDN		0x40 /* PLL power down */
+
+/* VBICNTL */
+
+/* RTSEL : control the real time signal output from the MPOUT pin */
+#define RTSEL_MASK  0x07
+#define RTSEL_VLOSS 0x00 /* 0000 = Video loss */
+#define RTSEL_HLOCK 0x01 /* 0001 = H-lock */
+#define RTSEL_SLOCK 0x02 /* 0010 = S-lock */
+#define RTSEL_VLOCK 0x03 /* 0011 = V-lock */
+#define RTSEL_MONO  0x04 /* 0100 = MONO */
+#define RTSEL_DET50 0x05 /* 0101 = DET50 */
+#define RTSEL_FIELD 0x06 /* 0110 = FIELD */
+#define RTSEL_RTCO  0x07 /* 0111 = RTCO ( Real Time Control ) */
+
+/* HSYNC start and end are constant for now */
+#define HSYNC_START	0x0260
+#define HSYNC_END	0x0300
+
+/*
+ * structure
+ */
+
+struct regval_list {
+	unsigned char reg_num;
+	unsigned char value;
+};
+
+struct tw9910_scale_ctrl {
+	char           *name;
+	unsigned short  width;
+	unsigned short  height;
+	u16             hscale;
+	u16             vscale;
+};
+
+struct tw9910_priv {
+	struct v4l2_subdev		subdev;
+	struct v4l2_clk			*clk;
+	struct tw9910_video_info	*info;
+	const struct tw9910_scale_ctrl	*scale;
+	v4l2_std_id			norm;
+	u32				revision;
+};
+
+static const struct tw9910_scale_ctrl tw9910_ntsc_scales[] = {
+	{
+		.name   = "NTSC SQ",
+		.width  = 640,
+		.height = 480,
+		.hscale = 0x0100,
+		.vscale = 0x0100,
+	},
+	{
+		.name   = "NTSC CCIR601",
+		.width  = 720,
+		.height = 480,
+		.hscale = 0x0100,
+		.vscale = 0x0100,
+	},
+	{
+		.name   = "NTSC SQ (CIF)",
+		.width  = 320,
+		.height = 240,
+		.hscale = 0x0200,
+		.vscale = 0x0200,
+	},
+	{
+		.name   = "NTSC CCIR601 (CIF)",
+		.width  = 360,
+		.height = 240,
+		.hscale = 0x0200,
+		.vscale = 0x0200,
+	},
+	{
+		.name   = "NTSC SQ (QCIF)",
+		.width  = 160,
+		.height = 120,
+		.hscale = 0x0400,
+		.vscale = 0x0400,
+	},
+	{
+		.name   = "NTSC CCIR601 (QCIF)",
+		.width  = 180,
+		.height = 120,
+		.hscale = 0x0400,
+		.vscale = 0x0400,
+	},
+};
+
+static const struct tw9910_scale_ctrl tw9910_pal_scales[] = {
+	{
+		.name   = "PAL SQ",
+		.width  = 768,
+		.height = 576,
+		.hscale = 0x0100,
+		.vscale = 0x0100,
+	},
+	{
+		.name   = "PAL CCIR601",
+		.width  = 720,
+		.height = 576,
+		.hscale = 0x0100,
+		.vscale = 0x0100,
+	},
+	{
+		.name   = "PAL SQ (CIF)",
+		.width  = 384,
+		.height = 288,
+		.hscale = 0x0200,
+		.vscale = 0x0200,
+	},
+	{
+		.name   = "PAL CCIR601 (CIF)",
+		.width  = 360,
+		.height = 288,
+		.hscale = 0x0200,
+		.vscale = 0x0200,
+	},
+	{
+		.name   = "PAL SQ (QCIF)",
+		.width  = 192,
+		.height = 144,
+		.hscale = 0x0400,
+		.vscale = 0x0400,
+	},
+	{
+		.name   = "PAL CCIR601 (QCIF)",
+		.width  = 180,
+		.height = 144,
+		.hscale = 0x0400,
+		.vscale = 0x0400,
+	},
+};
+
+/*
+ * general function
+ */
+static struct tw9910_priv *to_tw9910(const struct i2c_client *client)
+{
+	return container_of(i2c_get_clientdata(client), struct tw9910_priv,
+			    subdev);
+}
+
+static int tw9910_mask_set(struct i2c_client *client, u8 command,
+			   u8 mask, u8 set)
+{
+	s32 val = i2c_smbus_read_byte_data(client, command);
+	if (val < 0)
+		return val;
+
+	val &= ~mask;
+	val |= set & mask;
+
+	return i2c_smbus_write_byte_data(client, command, val);
+}
+
+static int tw9910_set_scale(struct i2c_client *client,
+			    const struct tw9910_scale_ctrl *scale)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(client, SCALE_HI,
+					(scale->vscale & 0x0F00) >> 4 |
+					(scale->hscale & 0x0F00) >> 8);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_write_byte_data(client, HSCALE_LO,
+					scale->hscale & 0x00FF);
+	if (ret < 0)
+		return ret;
+
+	ret = i2c_smbus_write_byte_data(client, VSCALE_LO,
+					scale->vscale & 0x00FF);
+
+	return ret;
+}
+
+static int tw9910_set_hsync(struct i2c_client *client)
+{
+	struct tw9910_priv *priv = to_tw9910(client);
+	int ret;
+
+	/* bit 10 - 3 */
+	ret = i2c_smbus_write_byte_data(client, HSBEGIN,
+					(HSYNC_START & 0x07F8) >> 3);
+	if (ret < 0)
+		return ret;
+
+	/* bit 10 - 3 */
+	ret = i2c_smbus_write_byte_data(client, HSEND,
+					(HSYNC_END & 0x07F8) >> 3);
+	if (ret < 0)
+		return ret;
+
+	/* So far only revisions 0 and 1 have been seen */
+	/* bit 2 - 0 */
+	if (1 == priv->revision)
+		ret = tw9910_mask_set(client, HSLOWCTL, 0x77,
+				      (HSYNC_START & 0x0007) << 4 |
+				      (HSYNC_END   & 0x0007));
+
+	return ret;
+}
+
+static void tw9910_reset(struct i2c_client *client)
+{
+	tw9910_mask_set(client, ACNTL1, SRESET, SRESET);
+	msleep(1);
+}
+
+static int tw9910_power(struct i2c_client *client, int enable)
+{
+	int ret;
+	u8 acntl1;
+	u8 acntl2;
+
+	if (enable) {
+		acntl1 = 0;
+		acntl2 = 0;
+	} else {
+		acntl1 = CLK_PDN | Y_PDN | C_PDN;
+		acntl2 = PLL_PDN;
+	}
+
+	ret = tw9910_mask_set(client, ACNTL1, ACNTL1_PDN_MASK, acntl1);
+	if (ret < 0)
+		return ret;
+
+	return tw9910_mask_set(client, ACNTL2, ACNTL2_PDN_MASK, acntl2);
+}
+
+static const struct tw9910_scale_ctrl *tw9910_select_norm(v4l2_std_id norm,
+							  u32 width, u32 height)
+{
+	const struct tw9910_scale_ctrl *scale;
+	const struct tw9910_scale_ctrl *ret = NULL;
+	__u32 diff = 0xffffffff, tmp;
+	int size, i;
+
+	if (norm & V4L2_STD_NTSC) {
+		scale = tw9910_ntsc_scales;
+		size = ARRAY_SIZE(tw9910_ntsc_scales);
+	} else if (norm & V4L2_STD_PAL) {
+		scale = tw9910_pal_scales;
+		size = ARRAY_SIZE(tw9910_pal_scales);
+	} else {
+		return NULL;
+	}
+
+	for (i = 0; i < size; i++) {
+		tmp = abs(width - scale[i].width) +
+			abs(height - scale[i].height);
+		if (tmp < diff) {
+			diff = tmp;
+			ret = scale + i;
+		}
+	}
+
+	return ret;
+}
+
+/*
+ * subdevice operations
+ */
+static int tw9910_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct tw9910_priv *priv = to_tw9910(client);
+	u8 val;
+	int ret;
+
+	if (!enable) {
+		switch (priv->revision) {
+		case 0:
+			val = OEN_TRI_SEL_ALL_OFF_r0;
+			break;
+		case 1:
+			val = OEN_TRI_SEL_ALL_OFF_r1;
+			break;
+		default:
+			dev_err(&client->dev, "un-supported revision\n");
+			return -EINVAL;
+		}
+	} else {
+		val = OEN_TRI_SEL_ALL_ON;
+
+		if (!priv->scale) {
+			dev_err(&client->dev, "norm select error\n");
+			return -EPERM;
+		}
+
+		dev_dbg(&client->dev, "%s %dx%d\n",
+			priv->scale->name,
+			priv->scale->width,
+			priv->scale->height);
+	}
+
+	ret = tw9910_mask_set(client, OPFORM, OEN_TRI_SEL_MASK, val);
+	if (ret < 0)
+		return ret;
+
+	return tw9910_power(client, enable);
+}
+
+static int tw9910_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct tw9910_priv *priv = to_tw9910(client);
+
+	*norm = priv->norm;
+
+	return 0;
+}
+
+static int tw9910_s_std(struct v4l2_subdev *sd, v4l2_std_id norm)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct tw9910_priv *priv = to_tw9910(client);
+	const unsigned hact = 720;
+	const unsigned hdelay = 15;
+	unsigned vact;
+	unsigned vdelay;
+	int ret;
+
+	if (!(norm & (V4L2_STD_NTSC | V4L2_STD_PAL)))
+		return -EINVAL;
+
+	priv->norm = norm;
+	if (norm & V4L2_STD_525_60) {
+		vact = 240;
+		vdelay = 18;
+		ret = tw9910_mask_set(client, VVBI, 0x10, 0x10);
+	} else {
+		vact = 288;
+		vdelay = 24;
+		ret = tw9910_mask_set(client, VVBI, 0x10, 0x00);
+	}
+	if (!ret)
+		ret = i2c_smbus_write_byte_data(client, CROP_HI,
+			((vdelay >> 2) & 0xc0) |
+			((vact >> 4) & 0x30) |
+			((hdelay >> 6) & 0x0c) |
+			((hact >> 8) & 0x03));
+	if (!ret)
+		ret = i2c_smbus_write_byte_data(client, VDELAY_LO,
+			vdelay & 0xff);
+	if (!ret)
+		ret = i2c_smbus_write_byte_data(client, VACTIVE_LO,
+			vact & 0xff);
+
+	return ret;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int tw9910_g_register(struct v4l2_subdev *sd,
+			     struct v4l2_dbg_register *reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	int ret;
+
+	if (reg->reg > 0xff)
+		return -EINVAL;
+
+	reg->size = 1;
+	ret = i2c_smbus_read_byte_data(client, reg->reg);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * ret      = int
+	 * reg->val = __u64
+	 */
+	reg->val = (__u64)ret;
+
+	return 0;
+}
+
+static int tw9910_s_register(struct v4l2_subdev *sd,
+			     const struct v4l2_dbg_register *reg)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	if (reg->reg > 0xff ||
+	    reg->val > 0xff)
+		return -EINVAL;
+
+	return i2c_smbus_write_byte_data(client, reg->reg, reg->val);
+}
+#endif
+
+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);
+}
+
+static int tw9910_set_frame(struct v4l2_subdev *sd, u32 *width, u32 *height)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct tw9910_priv *priv = to_tw9910(client);
+	int ret = -EINVAL;
+	u8 val;
+
+	/*
+	 * select suitable norm
+	 */
+	priv->scale = tw9910_select_norm(priv->norm, *width, *height);
+	if (!priv->scale)
+		goto tw9910_set_fmt_error;
+
+	/*
+	 * reset hardware
+	 */
+	tw9910_reset(client);
+
+	/*
+	 * set bus width
+	 */
+	val = 0x00;
+	if (SOCAM_DATAWIDTH_16 == priv->info->buswidth)
+		val = LEN;
+
+	ret = tw9910_mask_set(client, OPFORM, LEN, val);
+	if (ret < 0)
+		goto tw9910_set_fmt_error;
+
+	/*
+	 * select MPOUT behavior
+	 */
+	switch (priv->info->mpout) {
+	case TW9910_MPO_VLOSS:
+		val = RTSEL_VLOSS; break;
+	case TW9910_MPO_HLOCK:
+		val = RTSEL_HLOCK; break;
+	case TW9910_MPO_SLOCK:
+		val = RTSEL_SLOCK; break;
+	case TW9910_MPO_VLOCK:
+		val = RTSEL_VLOCK; break;
+	case TW9910_MPO_MONO:
+		val = RTSEL_MONO;  break;
+	case TW9910_MPO_DET50:
+		val = RTSEL_DET50; break;
+	case TW9910_MPO_FIELD:
+		val = RTSEL_FIELD; break;
+	case TW9910_MPO_RTCO:
+		val = RTSEL_RTCO;  break;
+	default:
+		val = 0;
+	}
+
+	ret = tw9910_mask_set(client, VBICNTL, RTSEL_MASK, val);
+	if (ret < 0)
+		goto tw9910_set_fmt_error;
+
+	/*
+	 * set scale
+	 */
+	ret = tw9910_set_scale(client, priv->scale);
+	if (ret < 0)
+		goto tw9910_set_fmt_error;
+
+	/*
+	 * set hsync
+	 */
+	ret = tw9910_set_hsync(client);
+	if (ret < 0)
+		goto tw9910_set_fmt_error;
+
+	*width = priv->scale->width;
+	*height = priv->scale->height;
+
+	return ret;
+
+tw9910_set_fmt_error:
+
+	tw9910_reset(client);
+	priv->scale = NULL;
+
+	return ret;
+}
+
+static int tw9910_get_selection(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_selection *sel)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct tw9910_priv *priv = to_tw9910(client);
+
+	if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE)
+		return -EINVAL;
+	/* Only CROP, CROP_DEFAULT and CROP_BOUNDS are supported */
+	if (sel->target > V4L2_SEL_TGT_CROP_BOUNDS)
+		return -EINVAL;
+
+	sel->r.left	= 0;
+	sel->r.top	= 0;
+	if (priv->norm & V4L2_STD_NTSC) {
+		sel->r.width	= 640;
+		sel->r.height	= 480;
+	} else {
+		sel->r.width	= 768;
+		sel->r.height	= 576;
+	}
+	return 0;
+}
+
+static int tw9910_get_fmt(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *mf = &format->format;
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct tw9910_priv *priv = to_tw9910(client);
+
+	if (format->pad)
+		return -EINVAL;
+
+	if (!priv->scale) {
+		priv->scale = tw9910_select_norm(priv->norm, 640, 480);
+		if (!priv->scale)
+			return -EINVAL;
+	}
+
+	mf->width	= priv->scale->width;
+	mf->height	= priv->scale->height;
+	mf->code	= MEDIA_BUS_FMT_UYVY8_2X8;
+	mf->colorspace	= V4L2_COLORSPACE_SMPTE170M;
+	mf->field	= V4L2_FIELD_INTERLACED_BT;
+
+	return 0;
+}
+
+static int tw9910_s_fmt(struct v4l2_subdev *sd,
+			struct v4l2_mbus_framefmt *mf)
+{
+	u32 width = mf->width, height = mf->height;
+	int ret;
+
+	WARN_ON(mf->field != V4L2_FIELD_ANY &&
+		mf->field != V4L2_FIELD_INTERLACED_BT);
+
+	/*
+	 * check color format
+	 */
+	if (mf->code != MEDIA_BUS_FMT_UYVY8_2X8)
+		return -EINVAL;
+
+	mf->colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+	ret = tw9910_set_frame(sd, &width, &height);
+	if (!ret) {
+		mf->width	= width;
+		mf->height	= height;
+	}
+	return ret;
+}
+
+static int tw9910_set_fmt(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *mf = &format->format;
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct tw9910_priv *priv = to_tw9910(client);
+	const struct tw9910_scale_ctrl *scale;
+
+	if (format->pad)
+		return -EINVAL;
+
+	if (V4L2_FIELD_ANY == mf->field) {
+		mf->field = V4L2_FIELD_INTERLACED_BT;
+	} else if (V4L2_FIELD_INTERLACED_BT != mf->field) {
+		dev_err(&client->dev, "Field type %d invalid.\n", mf->field);
+		return -EINVAL;
+	}
+
+	mf->code = MEDIA_BUS_FMT_UYVY8_2X8;
+	mf->colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+	/*
+	 * select suitable norm
+	 */
+	scale = tw9910_select_norm(priv->norm, mf->width, mf->height);
+	if (!scale)
+		return -EINVAL;
+
+	mf->width	= scale->width;
+	mf->height	= scale->height;
+
+	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE)
+		return tw9910_s_fmt(sd, mf);
+	cfg->try_fmt = *mf;
+	return 0;
+}
+
+static int tw9910_video_probe(struct i2c_client *client)
+{
+	struct tw9910_priv *priv = to_tw9910(client);
+	s32 id;
+	int ret;
+
+	/*
+	 * tw9910 only use 8 or 16 bit bus width
+	 */
+	if (SOCAM_DATAWIDTH_16 != priv->info->buswidth &&
+	    SOCAM_DATAWIDTH_8  != priv->info->buswidth) {
+		dev_err(&client->dev, "bus width error\n");
+		return -ENODEV;
+	}
+
+	ret = tw9910_s_power(&priv->subdev, 1);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * check and show Product ID
+	 * So far only revisions 0 and 1 have been seen
+	 */
+	id = i2c_smbus_read_byte_data(client, ID);
+	priv->revision = GET_REV(id);
+	id = GET_ID(id);
+
+	if (0x0B != id ||
+	    0x01 < priv->revision) {
+		dev_err(&client->dev,
+			"Product ID error %x:%x\n",
+			id, priv->revision);
+		ret = -ENODEV;
+		goto done;
+	}
+
+	dev_info(&client->dev,
+		 "tw9910 Product ID %0x:%0x\n", id, priv->revision);
+
+	priv->norm = V4L2_STD_NTSC;
+	priv->scale = &tw9910_ntsc_scales[0];
+
+done:
+	tw9910_s_power(&priv->subdev, 0);
+	return ret;
+}
+
+static const struct v4l2_subdev_core_ops tw9910_subdev_core_ops = {
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.g_register	= tw9910_g_register,
+	.s_register	= tw9910_s_register,
+#endif
+	.s_power	= tw9910_s_power,
+};
+
+static int tw9910_enum_mbus_code(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_mbus_code_enum *code)
+{
+	if (code->pad || code->index)
+		return -EINVAL;
+
+	code->code = MEDIA_BUS_FMT_UYVY8_2X8;
+	return 0;
+}
+
+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;
+}
+
+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);
+
+	/*
+	 * set OUTCTR1
+	 *
+	 * We use VVALID and DVALID signals to control VSYNC and HSYNC
+	 * outputs, in this mode their polarity is inverted.
+	 */
+	if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)
+		val |= HSP_HI;
+
+	if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)
+		val |= VSP_HI;
+
+	return i2c_smbus_write_byte_data(client, OUTCTR1, val);
+}
+
+static int tw9910_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *norm)
+{
+	*norm = V4L2_STD_NTSC | V4L2_STD_PAL;
+	return 0;
+}
+
+static const struct v4l2_subdev_video_ops tw9910_subdev_video_ops = {
+	.s_std		= tw9910_s_std,
+	.g_std		= tw9910_g_std,
+	.s_stream	= tw9910_s_stream,
+	.g_mbus_config	= tw9910_g_mbus_config,
+	.s_mbus_config	= tw9910_s_mbus_config,
+	.g_tvnorms	= tw9910_g_tvnorms,
+};
+
+static const struct v4l2_subdev_pad_ops tw9910_subdev_pad_ops = {
+	.enum_mbus_code = tw9910_enum_mbus_code,
+	.get_selection	= tw9910_get_selection,
+	.get_fmt	= tw9910_get_fmt,
+	.set_fmt	= tw9910_set_fmt,
+};
+
+static const struct v4l2_subdev_ops tw9910_subdev_ops = {
+	.core	= &tw9910_subdev_core_ops,
+	.video	= &tw9910_subdev_video_ops,
+	.pad	= &tw9910_subdev_pad_ops,
+};
+
+/*
+ * i2c_driver function
+ */
+
+static int tw9910_probe(struct i2c_client *client,
+			const struct i2c_device_id *did)
+
+{
+	struct tw9910_priv		*priv;
+	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) {
+		dev_err(&client->dev, "TW9910: missing platform data!\n");
+		return -EINVAL;
+	}
+
+	info = ssdd->drv_priv;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+		dev_err(&client->dev,
+			"I2C-Adapter doesn't support I2C_FUNC_SMBUS_BYTE_DATA\n");
+		return -EIO;
+	}
+
+	priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->info   = info;
+
+	v4l2_i2c_subdev_init(&priv->subdev, client, &tw9910_subdev_ops);
+
+	priv->clk = v4l2_clk_get(&client->dev, "mclk");
+	if (IS_ERR(priv->clk))
+		return PTR_ERR(priv->clk);
+
+	ret = tw9910_video_probe(client);
+	if (ret < 0)
+		v4l2_clk_put(priv->clk);
+
+	return ret;
+}
+
+static int tw9910_remove(struct i2c_client *client)
+{
+	struct tw9910_priv *priv = to_tw9910(client);
+	v4l2_clk_put(priv->clk);
+	return 0;
+}
+
+static const struct i2c_device_id tw9910_id[] = {
+	{ "tw9910", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tw9910_id);
+
+static struct i2c_driver tw9910_i2c_driver = {
+	.driver = {
+		.name = "tw9910",
+	},
+	.probe    = tw9910_probe,
+	.remove   = tw9910_remove,
+	.id_table = tw9910_id,
+};
+
+module_i2c_driver(tw9910_i2c_driver);
+
+MODULE_DESCRIPTION("SoC Camera driver for tw9910");
+MODULE_AUTHOR("Kuninori Morimoto");
+MODULE_LICENSE("GPL v2");
--
2.7.4

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

* [PATCH v1 10/10] media: i2c: tw9910: Remove soc_camera dependencies
  2017-11-15 10:55 [PATCH v1 00/10] Renesas Capture Engine Unit (CEU) V4L2 driver Jacopo Mondi
                   ` (8 preceding siblings ...)
  2017-11-15 10:56 ` [PATCH v1 09/10] v4l: i2c: Copy tw9910 soc_camera sensor driver Jacopo Mondi
@ 2017-11-15 10:56 ` Jacopo Mondi
  2017-12-11 14:55   ` Laurent Pinchart
  2017-12-13 12:13   ` Hans Verkuil
  9 siblings, 2 replies; 56+ messages in thread
From: Jacopo Mondi @ 2017-11-15 10:56 UTC (permalink / raw)
  To: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil
  Cc: Jacopo Mondi, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Remove soc_camera framework dependencies from tw9910 sensor driver.
- Handle clock directly
- Register async subdevice
- Add platform specific enable/disable functions
- Adjust build system

This commit does not remove the original soc_camera based driver.

Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
---
 drivers/media/i2c/Kconfig  |  9 ++++++
 drivers/media/i2c/Makefile |  1 +
 drivers/media/i2c/tw9910.c | 80 ++++++++++++++++++++++++++++++++++------------
 include/media/i2c/tw9910.h |  6 ++++
 4 files changed, 75 insertions(+), 21 deletions(-)

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index ff251ce..bbd77ee 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -415,6 +415,15 @@ config VIDEO_TW9906
 	  To compile this driver as a module, choose M here: the
 	  module will be called tw9906.

+config VIDEO_TW9910
+	tristate "Techwell TW9910 video decoder"
+	depends on VIDEO_V4L2 && I2C
+	---help---
+	  Support for Techwell TW9910 NTSC/PAL/SECAM video decoder.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called tw9910.
+
 config VIDEO_VPX3220
 	tristate "vpx3220a, vpx3216b & vpx3214c video decoders"
 	depends on VIDEO_V4L2 && I2C
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index b2459a1..835784a 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -48,6 +48,7 @@ obj-$(CONFIG_VIDEO_TVP7002) += tvp7002.o
 obj-$(CONFIG_VIDEO_TW2804) += tw2804.o
 obj-$(CONFIG_VIDEO_TW9903) += tw9903.o
 obj-$(CONFIG_VIDEO_TW9906) += tw9906.o
+obj-$(CONFIG_VIDEO_TW9910) += tw9910.o
 obj-$(CONFIG_VIDEO_CS3308) += cs3308.o
 obj-$(CONFIG_VIDEO_CS5345) += cs5345.o
 obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o
diff --git a/drivers/media/i2c/tw9910.c b/drivers/media/i2c/tw9910.c
index bdb5e0a..f422da2 100644
--- a/drivers/media/i2c/tw9910.c
+++ b/drivers/media/i2c/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,40 @@ 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;
+	}
+
+	if (priv->clk)
+		return clk_enable(priv->clk);
+
+	return 0;
+}
+
+static int tw9910_power_off(struct tw9910_priv *priv)
+{
+	if (priv->info->platform_enable)
+		priv->info->platform_disable();
+
+	if (priv->clk)
+		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 +640,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 +825,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 +885,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 +898,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 +956,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 +979,27 @@ 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);
+		goto error_put_clk;
+
+	ret = v4l2_async_register_subdev(&priv->subdev);
+	if (ret)
+		goto error_put_clk;
+
+	return ret;
+
+error_put_clk:
+	if (priv->clk)
+		clk_put(priv->clk);

 	return ret;
 }
@@ -973,7 +1007,11 @@ 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);
+	v4l2_device_unregister_subdev(&priv->subdev);
+
 	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] 56+ messages in thread

* Re: [PATCH v1 01/10] dt-bindings: media: Add Renesas CEU bindings
  2017-11-15 10:55 ` [PATCH v1 01/10] dt-bindings: media: Add Renesas CEU bindings Jacopo Mondi
@ 2017-11-15 11:32   ` Kieran Bingham
  2017-11-15 12:33   ` Sakari Ailus
  2017-11-15 13:07   ` Geert Uytterhoeven
  2 siblings, 0 replies; 56+ messages in thread
From: Kieran Bingham @ 2017-11-15 11:32 UTC (permalink / raw)
  To: Jacopo Mondi, laurent.pinchart, magnus.damm, geert, mchehab, hverkuil
  Cc: linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Jacopo,

A couple of minor language fixups inline.

On 15/11/17 10:55, Jacopo Mondi wrote:
> Add bindings documentation for Renesas Capture Engine Unit (CEU).
> 
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  .../devicetree/bindings/media/renesas,ceu.txt      | 87 ++++++++++++++++++++++
>  1 file changed, 87 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/renesas,ceu.txt
> 
> diff --git a/Documentation/devicetree/bindings/media/renesas,ceu.txt b/Documentation/devicetree/bindings/media/renesas,ceu.txt
> new file mode 100644
> index 0000000..a88e9cb
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/renesas,ceu.txt
> @@ -0,0 +1,87 @@
> +Renesas Capture Engine Unit (CEU)
> +----------------------------------------------
> +
> +The Capture Engine Unit is the image capture interface found on Renesas
> +RZ chip series and on SH Mobile ones.
> +
> +The interface supports a single parallel input with up 8/16bits data bus width.

s/with up 8/16bits/with either 8 or 16 bits/ ?

> +
> +Required properties:
> +- compatible
> +	Must be "renesas,renesas-ceu".
> +- reg
> +	Physical address base and size.
> +- interrupts
> +	The interrupt line number.
> +- pinctrl-names, pinctrl-0
> +	phandle of pin controller sub-node configuring pins for CEU operations.
> +
> +CEU supports a single parallel input and should contain a single 'port' subnode
> +with a single 'endpoint'. Optional endpoint properties applicable to parallel
> +input bus are described in "video-interfaces.txt".
> +
> +Example:
> +
> +The example describes the connection between the Capture Engine Unit and a

s/and a/and an/

> +OV7670 image sensor sitting on bus i2c1 with an on-board 24Mhz clock.
> +
> +ceu: ceu@e8210000 {
> +	reg = <0xe8210000 0x209c>;
> +	compatible = "renesas,renesas-ceu";
> +	interrupts = <GIC_SPI 332 IRQ_TYPE_LEVEL_HIGH>;
> +	pinctrl-names = "default";
> +	pinctrl-0 = <&vio_pins>;
> +
> +	status = "okay";
> +
> +	port {
> +		ceu_in: endpoint {
> +			remote-endpoint = <&ov7670_out>;
> +
> +			bus-width = <8>;
> +			hsync-active = <1>;
> +			vsync-active = <1>;
> +			pclk-sample = <1>;
> +			data-active = <1>;
> +		};
> +	};
> +};
> +
> +i2c1: i2c@fcfee400 {
> +	pinctrl-names = "default";
> +	pinctrl-0 = <&i2c1_pins>;
> +
> +	status = "okay";
> +	clock-frequency = <100000>;
> +
> +	ov7670: camera@21 {
> +		compatible = "ovti,ov7670";
> +		reg = <0x21>;
> +
> +		pinctrl-names = "default";
> +		pinctrl-0 = <&vio_pins>;
> +
> +		reset-gpios = <&port3 11 GPIO_ACTIVE_LOW>;
> +		powerdown-gpios = <&port3 12 GPIO_ACTIVE_HIGH>;
> +
> +		clocks = <&xclk>;
> +		clock-names = "xclk";
> +
> +		xclk: fixed_clk {
> +			compatible = "fixed-clock";
> +			#clock-cells = <0>;
> +			clock-frequency = <24000000>;
> +		};
> +
> +		port {
> +			ov7670_out: endpoint {
> +				remote-endpoint = <&ceu_in>;
> +
> +				bus-width = <8>;
> +				hsync-active = <1>;
> +				vsync-active = <1>;
> +				pclk-sample = <1>;
> +				data-active = <1>;
> +			};
> +		};
> +	};
> 

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

* Re: [PATCH v1 01/10] dt-bindings: media: Add Renesas CEU bindings
  2017-11-15 10:55 ` [PATCH v1 01/10] dt-bindings: media: Add Renesas CEU bindings Jacopo Mondi
  2017-11-15 11:32   ` Kieran Bingham
@ 2017-11-15 12:33   ` Sakari Ailus
  2017-12-11 14:24     ` Laurent Pinchart
  2017-11-15 13:07   ` Geert Uytterhoeven
  2 siblings, 1 reply; 56+ messages in thread
From: Sakari Ailus @ 2017-11-15 12:33 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Jacopo,

Thanks for the patchset. Please see my comments below.

On Wed, Nov 15, 2017 at 11:55:54AM +0100, Jacopo Mondi wrote:
> Add bindings documentation for Renesas Capture Engine Unit (CEU).
> 
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  .../devicetree/bindings/media/renesas,ceu.txt      | 87 ++++++++++++++++++++++
>  1 file changed, 87 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/renesas,ceu.txt
> 
> diff --git a/Documentation/devicetree/bindings/media/renesas,ceu.txt b/Documentation/devicetree/bindings/media/renesas,ceu.txt
> new file mode 100644
> index 0000000..a88e9cb
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/renesas,ceu.txt
> @@ -0,0 +1,87 @@
> +Renesas Capture Engine Unit (CEU)
> +----------------------------------------------
> +
> +The Capture Engine Unit is the image capture interface found on Renesas
> +RZ chip series and on SH Mobile ones.
> +
> +The interface supports a single parallel input with up 8/16bits data bus width.
> +
> +Required properties:
> +- compatible
> +	Must be "renesas,renesas-ceu".
> +- reg
> +	Physical address base and size.
> +- interrupts
> +	The interrupt line number.
> +- pinctrl-names, pinctrl-0
> +	phandle of pin controller sub-node configuring pins for CEU operations.
> +
> +CEU supports a single parallel input and should contain a single 'port' subnode
> +with a single 'endpoint'. Optional endpoint properties applicable to parallel
> +input bus are described in "video-interfaces.txt".

Could you list which ones they are? For someone not familiar with the
parallel bus this might not be obvious; also not all hardware can make use
of every one of these properties.

> +
> +Example:
> +
> +The example describes the connection between the Capture Engine Unit and a
> +OV7670 image sensor sitting on bus i2c1 with an on-board 24Mhz clock.
> +
> +ceu: ceu@e8210000 {
> +	reg = <0xe8210000 0x209c>;
> +	compatible = "renesas,renesas-ceu";
> +	interrupts = <GIC_SPI 332 IRQ_TYPE_LEVEL_HIGH>;
> +	pinctrl-names = "default";
> +	pinctrl-0 = <&vio_pins>;
> +
> +	status = "okay";
> +
> +	port {
> +		ceu_in: endpoint {
> +			remote-endpoint = <&ov7670_out>;
> +
> +			bus-width = <8>;
> +			hsync-active = <1>;
> +			vsync-active = <1>;
> +			pclk-sample = <1>;
> +			data-active = <1>;
> +		};
> +	};
> +};
> +
> +i2c1: i2c@fcfee400 {
> +	pinctrl-names = "default";
> +	pinctrl-0 = <&i2c1_pins>;
> +
> +	status = "okay";
> +	clock-frequency = <100000>;
> +
> +	ov7670: camera@21 {
> +		compatible = "ovti,ov7670";
> +		reg = <0x21>;
> +
> +		pinctrl-names = "default";
> +		pinctrl-0 = <&vio_pins>;
> +
> +		reset-gpios = <&port3 11 GPIO_ACTIVE_LOW>;
> +		powerdown-gpios = <&port3 12 GPIO_ACTIVE_HIGH>;
> +
> +		clocks = <&xclk>;
> +		clock-names = "xclk";
> +
> +		xclk: fixed_clk {
> +			compatible = "fixed-clock";
> +			#clock-cells = <0>;
> +			clock-frequency = <24000000>;
> +		};

What's the purpose of the fixed_clk node here?

> +
> +		port {
> +			ov7670_out: endpoint {
> +				remote-endpoint = <&ceu_in>;
> +
> +				bus-width = <8>;
> +				hsync-active = <1>;
> +				vsync-active = <1>;
> +				pclk-sample = <1>;
> +				data-active = <1>;
> +			};
> +		};
> +	};

-- 
Regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi

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

* Re: [PATCH v1 02/10] include: media: Add Renesas CEU driver interface
  2017-11-15 10:55 ` [PATCH v1 02/10] include: media: Add Renesas CEU driver interface Jacopo Mondi
@ 2017-11-15 12:36   ` Sakari Ailus
  2017-12-11 14:26     ` Laurent Pinchart
  0 siblings, 1 reply; 56+ messages in thread
From: Sakari Ailus @ 2017-11-15 12:36 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Jacopo,

On Wed, Nov 15, 2017 at 11:55:55AM +0100, Jacopo Mondi wrote:
> Add renesas-ceu header file.
> 
> Do not remove the existing sh_mobile_ceu.h one as long as the original
> driver does not go away.

Hmm. This isn't really not about not removing a file but adding a new one.
Do you really need it outside the driver's own directory?

> 
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  include/media/drv-intf/renesas-ceu.h | 23 +++++++++++++++++++++++
>  1 file changed, 23 insertions(+)
>  create mode 100644 include/media/drv-intf/renesas-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..f2da78c
> --- /dev/null
> +++ b/include/media/drv-intf/renesas-ceu.h
> @@ -0,0 +1,23 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +#ifndef __ASM_RENESAS_CEU_H__
> +#define __ASM_RENESAS_CEU_H__
> +
> +#include <media/v4l2-mediabus.h>
> +
> +#define CEU_FLAG_PRIMARY_SENS	BIT(0)
> +#define CEU_MAX_SENS		2
> +
> +struct ceu_async_subdev {
> +	unsigned long flags;
> +	unsigned char bus_width;
> +	unsigned char bus_shift;
> +	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__ */
> --
> 2.7.4
> 

-- 
Sakari Ailus
e-mail: sakari.ailus@iki.fi

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-11-15 10:55 ` [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver Jacopo Mondi
@ 2017-11-15 12:45   ` Sakari Ailus
  2017-11-15 14:25     ` jacopo mondi
  2017-12-11 16:15   ` Laurent Pinchart
  2017-12-13 12:03   ` Hans Verkuil
  2 siblings, 1 reply; 56+ messages in thread
From: Sakari Ailus @ 2017-11-15 12:45 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Jacopo,

Could you remove the original driver and send the patch using git
send-email -C ? That way a single patch would address converting it to a
proper V4L2 driver as well as move it to the correct location. The changes
would be easier to review that way since then, well, it'd be easier to see
the changes. :-)

The same goes for the two V4L2 SoC camera sensor / video decoder drivers at
the end of the set.

On Wed, Nov 15, 2017 at 11:55:56AM +0100, Jacopo Mondi wrote:
> Add driver for Renesas Capture Engine Unit (CEU).
> 
> The CEU interface supports capturing 'data' (YUV422) and 'images'
> (NV[12|21|16|61]).
> 
> This driver aims to replace the soc_camera based sh_mobile_ceu one.
> 
> Tested with ov7670 camera sensor, providing YUYV_2X8 data on Renesas RZ
> platform GR-Peach.
> 
> Tested with ov7725 camera sensor on SH4 platform Migo-R.

Nice!

> 
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  drivers/media/platform/Kconfig       |    9 +
>  drivers/media/platform/Makefile      |    2 +
>  drivers/media/platform/renesas-ceu.c | 1768 ++++++++++++++++++++++++++++++++++
>  3 files changed, 1779 insertions(+)
>  create mode 100644 drivers/media/platform/renesas-ceu.c
> 
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 3c4f7fa..401caea 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -144,6 +144,15 @@ config VIDEO_STM32_DCMI
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called stm32-dcmi.
> 
> +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
> +	select V4L2_FWNODE
> +	---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 327f80a..0d1f02b 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -27,6 +27,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_MUX)			+= video-mux.o
> diff --git a/drivers/media/platform/renesas-ceu.c b/drivers/media/platform/renesas-ceu.c
> new file mode 100644
> index 0000000..aaba3cd
> --- /dev/null
> +++ b/drivers/media/platform/renesas-ceu.c
> @@ -0,0 +1,1768 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * V4L2 Driver for Renesas Capture Engine Unit (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>

Do you need this header? There would seem some that I wouldn't expect to be
needed below, such as linux/init.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/of_graph.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-fwnode.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-image-sizes.h>
> +#include <media/v4l2-mediabus.h>
> +#include <media/videobuf2-dma-contig.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_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_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_CFWCR	0x5c /* Firewall operation control register	*/
> +#define CEU_CDOCR	0x64 /* Capture data output control 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		*/
> +
> +/* Data synchronous fetch mode */
> +#define CEU_CAMCR_JPEG			BIT(4)
> +
> +/* Input components ordering: CEU_CAMCR.DTARY field */
> +#define CEU_CAMCR_DTARY_8_UYVY		(0x00 << 8)
> +#define CEU_CAMCR_DTARY_8_VYUY		(0x01 << 8)
> +#define CEU_CAMCR_DTARY_8_YUYV		(0x02 << 8)
> +#define CEU_CAMCR_DTARY_8_YVYU		(0x03 << 8)
> +/* TODO: input components ordering for 16 bits input */
> +
> +/* Bus transfer MTU */
> +#define CEU_CAPCR_BUS_WIDTH256		(0x3 << 20)
> +
> +/* Bus width configuration */
> +#define CEU_CAMCR_DTIF_16BITS		BIT(12)
> +
> +/* No downsampling to planar YUV420 in image fetch mode */
> +#define CEU_CDOCR_NO_DOWSAMPLE		BIT(4)
> +
> +/* Swap all input data in 8-bit, 16-bits and 32-bits units (Figure 46.45) */
> +#define CEU_CDOCR_SWAP_ENDIANNESS	(7)
> +
> +/* Capture reset and enable bits */
> +#define CEU_CAPSR_CPKIL			BIT(16)
> +#define CEU_CAPSR_CE			BIT(0)
> +
> +/* CEU operating flag bit */
> +#define CEU_CAPCR_CTNCP			BIT(16)
> +#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)
> +#define CEU_CEIER_MASK			(CEU_CEIER_CPE | CEU_CEIER_VBP)
> +
> +/* mbus configuration flags */
> +#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
> + * @bpp: number of bits per pixels unit
> + */
> +struct ceu_mbus_fmt {
> +	u32	mbus_code;
> +	u32	fmt_order;
> +	u32	fmt_order_swap;
> +	bool	swapped;
> +	u8	bps;
> +	u8	bpp;
> +};
> +
> +/**
> + * ceu_buffer - Link vb2 buffer to the list of available 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_subdev - Wraps v4l2 sub-device and provides async subdevice.
> + */
> +struct ceu_subdev {
> +	struct v4l2_subdev *v4l2_sd;
> +	struct v4l2_async_subdev asd;
> +
> +	/* per-subdevice mbus configuration options */
> +	unsigned int mbus_flags;
> +	struct ceu_mbus_fmt mbus_fmt;
> +};
> +
> +static struct ceu_subdev *to_ceu_subdev(struct v4l2_async_subdev *asd)
> +{
> +	return container_of(asd, struct ceu_subdev, asd);
> +}
> +
> +/**
> + * ceu_device - CEU device instance
> + */
> +struct ceu_device {
> +	struct device		*dev;
> +	struct video_device	vdev;
> +	struct v4l2_device	v4l2_dev;
> +
> +	/* subdevices descriptors */
> +	struct ceu_subdev	*subdevs;
> +	/* the subdevice currently in use */
> +	struct ceu_subdev	*sd;
> +	unsigned int		sd_index;
> +	unsigned int		num_sd;
> +
> +	/* currently configured field and pixel format */
> +	enum v4l2_field	field;
> +	struct v4l2_pix_format_mplane v4l2_pix;
> +
> +	/* async subdev notification helpers */
> +	struct v4l2_async_notifier notifier;
> +	/* pointers to "struct ceu_subdevice -> asd" */
> +	struct v4l2_async_subdev **asds;
> +
> +	/* 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;
> +
> +	/* base - CEU memory base address */
> +	void __iomem	*base;
> +};
> +
> +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
> + * capabilities.
> + */
> +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,
> +	},
> +	{
> +		.fourcc	= V4L2_PIX_FMT_YUYV,
> +		.bpp	= 16,
> +	},
> +};
> +
> +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;
> +}
> +
> +static inline bool ceu_is_fmt_planar(struct v4l2_pix_format_mplane *pix)
> +{
> +	switch (pix->pixelformat) {
> +	case V4L2_PIX_FMT_YUYV:
> +		return false;
> +	case V4L2_PIX_FMT_NV16:
> +	case V4L2_PIX_FMT_NV61:
> +	case V4L2_PIX_FMT_NV12:
> +	case V4L2_PIX_FMT_NV21:
> +		return true;
> +	}
> +
> +	return true;
> +}
> +
> +/* ----------------------------------------------------------------------------
> + * 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
> + */
> +static int ceu_soft_reset(struct ceu_device *ceudev)
> +{
> +	unsigned int reset_done;
> +	unsigned int i;
> +
> +	ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CPKIL);
> +
> +	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) {
> +		v4l2_err(&ceudev->v4l2_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 capture.
> + */
> +static int ceu_capture(struct ceu_device *ceudev)
> +{
> +	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> +	dma_addr_t phys_addr_top;
> +	u32 status;
> +
> +	/* Clean interrupt status and re-enable interrupts */
> +	status = ceu_read(ceudev, CEU_CETCR);
> +	ceu_write(ceudev, CEU_CEIER,
> +		  ceu_read(ceudev, CEU_CEIER) & ~CEU_CEIER_MASK);
> +	ceu_write(ceudev, CEU_CETCR, ~status & CEU_CETCR_MAGIC);
> +	ceu_write(ceudev, CEU_CEIER, CEU_CEIER_MASK);
> +	ceu_write(ceudev, CEU_CAPCR,
> +		  ceu_read(ceudev, CEU_CAPCR) & ~CEU_CAPCR_CTNCP);
> +
> +	/*
> +	 * 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) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "VBP interrupt while capturing\n");
> +		ceu_soft_reset(ceudev);
> +		return -EIO;
> +	} else if (!ceudev->active) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "No available buffers for capture\n");
> +		return -EINVAL;
> +	}
> +
> +	phys_addr_top =
> +		vb2_dma_contig_plane_dma_addr(&ceudev->active->vb2_buf, 0);
> +	ceu_write(ceudev, CEU_CDAYR, phys_addr_top);
> +
> +	/* Ignore CbCr plane in data sync mode */
> +	if (ceu_is_fmt_planar(pix)) {
> +		phys_addr_top =
> +			vb2_dma_contig_plane_dma_addr(&ceudev->active->vb2_buf,
> +						      1);
> +		ceu_write(ceudev, CEU_CDACR, phys_addr_top);
> +	}
> +
> +	/*
> +	 * 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 0;
> +}
> +
> +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;
> +
> +	/* Prepare a new 'active' buffer and trigger a new capture */
> +	buf = vb2_to_ceu(vbuf);
> +	vbuf->vb2_buf.timestamp = ktime_get_ns();
> +
> +	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;
> +	}
> +
> +	/*
> +	 * If the new capture started successfully, mark the previous buffer
> +	 * as "DONE".
> +	 */
> +	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_calc_plane_sizes() - Fill 'struct v4l2_plane_pix_format' per plane
> + *			    information according to the currently configured
> + *			    pixel format.
> + */
> +static int ceu_calc_plane_sizes(struct ceu_device *ceudev,
> +				const struct ceu_fmt *ceu_fmt,
> +				struct v4l2_pix_format_mplane *pix)
> +{
> +	struct v4l2_plane_pix_format *plane_fmt = &pix->plane_fmt[0];
> +
> +	switch (pix->pixelformat) {
> +	case V4L2_PIX_FMT_YUYV:
> +		pix->num_planes			= 1;
> +		plane_fmt[0].bytesperline	= pix->width * ceu_fmt->bpp / 8;
> +		plane_fmt[0].sizeimage		= pix->height *
> +						  plane_fmt[0].bytesperline;
> +		break;
> +	case V4L2_PIX_FMT_NV16:
> +	case V4L2_PIX_FMT_NV61:
> +		pix->num_planes			= 2;
> +		plane_fmt[0].bytesperline	= pix->width;
> +		plane_fmt[0].sizeimage		= pix->height * pix->width;
> +		plane_fmt[1]			= plane_fmt[0];
> +		break;
> +	case V4L2_PIX_FMT_NV12:
> +	case V4L2_PIX_FMT_NV21:
> +		pix->num_planes			= 2;
> +		plane_fmt[0].bytesperline	= pix->width;
> +		plane_fmt[0].sizeimage		= pix->height * pix->width;
> +		plane_fmt[1].bytesperline	= pix->width;
> +		plane_fmt[1].sizeimage		= pix->height * pix->width / 2;
> +		break;
> +	default:
> +		pix->num_planes			= 0;
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Format 0x%x not supported\n", pix->pixelformat);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * 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_mplane *pix = &ceudev->v4l2_pix;
> +	unsigned int i;
> +
> +	if (!vq->num_buffers)
> +		ceudev->sequence = 0;
> +
> +	if (!*count)
> +		*count = 2;
> +
> +	/* num_planes is set: just check plane sizes */
> +	if (*num_planes) {
> +		for (i = 0; i < pix->num_planes; i++) {
> +			if (sizes[i] < pix->plane_fmt[i].sizeimage)
> +				return -EINVAL;
> +		}
> +
> +		return 0;
> +	}
> +
> +	/* num_planes not set: called from REQBUFS, just set plane sizes */
> +	*num_planes = pix->num_planes;
> +	for (i = 0; i < pix->num_planes; i++)
> +		sizes[i] = pix->plane_fmt[i].sizeimage;
> +
> +	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 v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> +	struct ceu_buffer *buf = vb2_to_ceu(vbuf);
> +	unsigned long irqflags;
> +	unsigned int i;
> +
> +	for (i = 0; i < pix->num_planes; i++) {
> +		if (vb2_plane_size(vb, i) < pix->plane_fmt[i].sizeimage) {
> +			v4l2_err(&ceudev->v4l2_dev,
> +				 "Buffer #%d too small (%lu < %u)\n",
> +				 vb->index, vb2_plane_size(vb, i),
> +				 pix->plane_fmt[i].sizeimage);
> +			goto error;
> +		}
> +
> +		vb2_set_plane_payload(vb, i, pix->plane_fmt[i].sizeimage);
> +	}
> +
> +	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 v4l2_subdev *v4l2_sd = ceudev->sd->v4l2_sd;
> +	struct ceu_buffer *buf;
> +	unsigned long irqflags;
> +	int ret = 0;
> +
> +	ret = v4l2_subdev_call(v4l2_sd, video, s_stream, 1);
> +	if (ret && ret != -ENOIOCTLCMD) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Subdevice failed to start streaming: %d\n", ret);
> +		goto error_return_bufs;
> +	}
> +
> +	spin_lock_irqsave(&ceudev->lock, irqflags);
> +	ceudev->sequence = 0;
> +
> +	if (ceudev->active) {
> +		ret = -EINVAL;
> +		spin_unlock_irqrestore(&ceudev->lock, irqflags);
> +		goto error_stop_sensor;
> +	}
> +
> +	/* Grab the first available buffer and trigger the first capture. */
> +	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(v4l2_sd, 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 v4l2_subdev *v4l2_sd = ceudev->sd->v4l2_sd;
> +	struct ceu_buffer *buf;
> +	unsigned long irqflags;
> +
> +	v4l2_subdev_call(v4l2_sd, 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(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> +	INIT_LIST_HEAD(&ceudev->capture);
> +
> +	spin_unlock_irqrestore(&ceudev->lock, irqflags);
> +
> +	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 errors
> + *	    0 if g_mbus_config is not supported,
> + *	    > 0  for bus configuration flags supported by (ceu AND sensor)
> + */
> +static int ceu_test_mbus_param(struct ceu_device *ceudev)
> +{
> +	struct v4l2_subdev *sd = ceudev->sd->v4l2_sd;
> +	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_bus_params() - Configure CEU interface registers using bus
> + *			  parameters
> + */
> +static int ceu_set_bus_params(struct ceu_device *ceudev)
> +{
> +	u32 camcr = 0, cdocr = 0, cfzsr = 0, cdwdr = 0, capwr = 0;
> +	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> +	struct ceu_subdev *ceu_sd = ceudev->sd;
> +	struct ceu_mbus_fmt *mbus_fmt = &ceu_sd->mbus_fmt;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	unsigned int mbus_flags = ceu_sd->mbus_flags;
> +	unsigned long common_flags = CEU_BUS_FLAGS;
> +	int ret;
> +	struct v4l2_mbus_config cfg = {
> +		.type = V4L2_MBUS_PARALLEL,
> +	};
> +
> +	/*
> +	 * If client doesn't implement g_mbus_config, we just use our
> +	 * platform data.
> +	 */
> +	ret = ceu_test_mbus_param(ceudev);
> +	if (ret < 0)
> +		return ret;
> +	else if (ret == 0)
> +		common_flags = ceudev->sd->mbus_flags;
> +	else
> +		common_flags = ret;
> +
> +	/*
> +	 * If the we can choose between multiple alternatives select
> +	 * active high polarities.
> +	 */
> +	if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) &&
> +	    (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) {
> +		if (mbus_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
> +			common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW;
> +		else
> +			common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH;
> +	}
> +
> +	if ((common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) &&
> +	    (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) {
> +		if (mbus_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
> +			common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_LOW;
> +		else
> +			common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_HIGH;
> +	}
> +
> +	cfg.flags = common_flags;
> +	ret = v4l2_subdev_call(v4l2_sd, video, s_mbus_config, &cfg);
> +	if (ret < 0 && ret != -ENOIOCTLCMD)
> +		return ret;
> +
> +	/* Start configuring CEU registers */
> +	ceu_write(ceudev, CEU_CAIFR, 0);
> +	ceu_write(ceudev, CEU_CFWCR, 0);
> +	ceu_write(ceudev, CEU_CRCNTR, 0);
> +	ceu_write(ceudev, CEU_CRCMPR, 0);
> +
> +	/* Set the frame capture period for both image capture and data sync */
> +	capwr = (pix->height << 16) | pix->width * mbus_fmt->bpp / 8;
> +
> +	/*
> +	 * Swap input data endianness by default.
> +	 * 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)
> +	 *
> +	 * Use CEU_CDOCR[2:0] to swap data ordering.
> +	 */
> +	cdocr = CEU_CDOCR_SWAP_ENDIANNESS;
> +
> +	/*
> +	 * Configure CAMCR and CDOCR:
> +	 * match input components ordering with memory output format and
> +	 * handle downsampling to YUV420.
> +	 *
> +	 * If the memory output planar format is 'swapped' (Cr before Cb) and
> +	 * input format is not, use the swapped version of CAMCR.DTARY.
> +	 *
> +	 * If the memory output planar 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
> +	 *
> +	 * No downsample for data fetch sync mode.
> +	 */
> +	switch (pix->pixelformat) {
> +	/* data fetch sync mode */
> +	case V4L2_PIX_FMT_YUYV:
> +		/* TODO: handle YUYV permutations through DTARY bits */
> +		camcr	|= CEU_CAMCR_JPEG;
> +		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
> +		cfzsr	= (pix->height << 16) | pix->width;
> +		cdwdr	= pix->plane_fmt[0].bytesperline;
> +		break;
> +
> +	/* non-swapped planar image capture mode */
> +	case V4L2_PIX_FMT_NV16:
> +		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
> +	case V4L2_PIX_FMT_NV12:
> +		if (mbus_fmt->swapped)
> +			camcr |= mbus_fmt->fmt_order_swap;
> +		else
> +			camcr |= mbus_fmt->fmt_order;
> +
> +		cfzsr	= (pix->height << 16) | pix->width;
> +		cdwdr	= pix->width;
> +		break;
> +
> +	/* swapped planar image capture mode */
> +	case V4L2_PIX_FMT_NV61:
> +		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
> +	case V4L2_PIX_FMT_NV21:
> +		if (mbus_fmt->swapped)
> +			camcr |= mbus_fmt->fmt_order;
> +		else
> +			camcr |= mbus_fmt->fmt_order_swap;
> +
> +		cfzsr	= (pix->height << 16) | pix->width;
> +		cdwdr	= pix->width;
> +		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 with DTIF bit in CAMCR */
> +	ceu_write(ceudev, CEU_CAMCR, camcr);
> +	ceu_write(ceudev, CEU_CDOCR, cdocr);
> +	ceu_write(ceudev, CEU_CAPCR, CEU_CAPCR_BUS_WIDTH256);
> +
> +	/*
> +	 * TODO: make CAMOR offsets configurable.
> +	 * CAMOR wants to know the number of blanks between a VS/HS signal
> +	 * and valid data. This value should actually come from the sensor...
> +	 */
> +	ceu_write(ceudev, CEU_CAMOR, 0);
> +
> +	/* TODO: 16 bit bus width require re-calculation of cdwdr and cfzsr */
> +	ceu_write(ceudev, CEU_CAPWR, capwr);
> +	ceu_write(ceudev, CEU_CFSZR, cfzsr);
> +	ceu_write(ceudev, CEU_CDWDR, cdwdr);
> +
> +	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 v4l2_pix_format_mplane *pix = &v4l2_fmt->fmt.pix_mp;
> +	struct ceu_subdev *ceu_sd = ceudev->sd;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	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_YUYV:
> +	case V4L2_PIX_FMT_NV16:
> +	case V4L2_PIX_FMT_NV61:
> +	case V4L2_PIX_FMT_NV12:
> +	case V4L2_PIX_FMT_NV21:
> +		break;
> +
> +	default:
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Pixel format 0x%x not supported, default to NV16\n",
> +			 pix->pixelformat);
> +		pix->pixelformat = V4L2_PIX_FMT_NV16;
> +	}
> +
> +	ceu_fmt = get_ceu_fmt_from_fourcc(pix->pixelformat);
> +
> +	/* 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 used to produce memory
> +	 * format is selected at initialization time
> +	 */
> +	v4l2_fill_mbus_format_mplane(&sd_format.format, pix);
> +	ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, &pad_cfg, &sd_format);
> +	if (ret)
> +		return ret;
> +
> +	/* Scale down to sensor supported sizes */
> +	if (sd_format.format.width != pix->width ||
> +	    sd_format.format.height != pix->height) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Unable to apply: 0x%x - %ux%u - scale to %ux%u\n",
> +			 pix->pixelformat, pix->width, pix->height,
> +			 sd_format.format.width, sd_format.format.height);
> +		pix->width = sd_format.format.width;
> +		pix->height = sd_format.format.height;
> +	}
> +
> +	/* Calculate per-plane sizes based on image format */
> +	v4l2_fill_pix_format_mplane(pix, &sd_format.format);
> +	pix->field = V4L2_FIELD_NONE;
> +	ret = ceu_calc_plane_sizes(ceudev, ceu_fmt, pix);
> +	if (ret < 0)
> +		return ret;
> +
> +	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_subdev *ceu_sd = ceudev->sd;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	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_mplane(&format.format, &v4l2_fmt->fmt.pix_mp);
> +	ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, NULL, &format);
> +	if (ret)
> +		return ret;
> +
> +	ceudev->v4l2_pix = v4l2_fmt->fmt.pix_mp;
> +
> +	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_MPLANE,
> +		.fmt.pix_mp = {
> +			.width		= VGA_WIDTH,
> +			.height		= VGA_HEIGHT,
> +			.field		= V4L2_FIELD_NONE,
> +			.pixelformat	= V4L2_PIX_FMT_NV16,
> +			.plane_fmt	= {
> +				[0]	= {
> +					.sizeimage = VGA_WIDTH * VGA_HEIGHT * 2,
> +					.bytesperline = VGA_WIDTH * 2,
> +				},
> +				[1]	= {
> +					.sizeimage = VGA_WIDTH * VGA_HEIGHT * 2,
> +					.bytesperline = VGA_WIDTH * 2,
> +				},
> +			},
> +		},
> +	};
> +
> +	ret = ceu_try_fmt(ceudev, &v4l2_fmt);
> +	if (ret)
> +		return ret;
> +
> +	ceudev->v4l2_pix = v4l2_fmt.fmt.pix_mp;
> +
> +	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;
> + * - YUYV422 if sensor provides YUYV422
> + *
> + * TODO: Other YUYV422 permutations throug data fetch sync mode and DTARY
> + * TODO: Binary data (eg. JPEG) and raw formats through data fetch sync mode
> + */
> +static int ceu_init_formats(struct ceu_device *ceudev)
> +{
> +	struct ceu_subdev *ceu_sd = ceudev->sd;
> +	struct ceu_mbus_fmt *mbus_fmt = &ceu_sd->mbus_fmt;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	bool yuyv_bus_fmt = false;
> +
> +	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 YUYV422 */
> +	while (!yuyv_bus_fmt &&
> +	       !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:
> +		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;
> +		}
> +
> +		sd_mbus_fmt.index++;
> +	}
> +
> +	if (!yuyv_bus_fmt)
> +		return -ENXIO;
> +
> +	/*
> +	 * Save the first encountered YUYV format as "mbus_fmt" and use it
> +	 * to output all planar YUV422 and YUV420 (NV*) formats to memory as
> +	 * well as for data synch fetch mode (YUYV - YVYU etc. ).
> +	 */
> +	mbus_fmt->mbus_code	= sd_mbus_fmt.code;
> +	mbus_fmt->bps		= 8;
> +
> +	/* Annotate the selected bus format components ordering */
> +	switch (sd_mbus_fmt.code) {
> +	case MEDIA_BUS_FMT_YUYV8_2X8:
> +		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_YUYV;
> +		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_YVYU;
> +		mbus_fmt->swapped		= false;
> +		mbus_fmt->bpp			= 16;
> +		break;
> +
> +	case MEDIA_BUS_FMT_YVYU8_2X8:
> +		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_YVYU;
> +		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_YUYV;
> +		mbus_fmt->swapped		= true;
> +		mbus_fmt->bpp			= 16;
> +		break;
> +
> +	case MEDIA_BUS_FMT_UYVY8_2X8:
> +		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_UYVY;
> +		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_VYUY;
> +		mbus_fmt->swapped		= false;
> +		mbus_fmt->bpp			= 16;
> +		break;
> +
> +	case MEDIA_BUS_FMT_VYUY8_2X8:
> +		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_VYUY;
> +		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_UYVY;
> +		mbus_fmt->swapped		= true;
> +		mbus_fmt->bpp			= 16;
> +		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 *v4l2_sd = ceudev->sd->v4l2_sd;
> +
> +	v4l2_subdev_call(v4l2_sd, 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 *v4l2_sd = ceudev->sd->v4l2_sd;
> +
> +	v4l2_subdev_call(v4l2_sd, 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_g_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;
> +
> +	f->type	= V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> +	f->fmt.pix_mp = ceudev->v4l2_pix;
> +
> +	return 0;
> +}
> +
> +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)
> +{
> +	struct ceu_device *ceudev = video_drvdata(file);
> +
> +	*i = ceudev->sd_index;
> +
> +	return 0;
> +}
> +
> +static int ceu_s_input(struct file *file, void *priv, unsigned int i)
> +{
> +	struct ceu_device *ceudev = video_drvdata(file);
> +	struct ceu_subdev *ceu_sd_old;
> +	int ret;
> +
> +	if (i >= ceudev->num_sd)
> +		return -EINVAL;
> +
> +	ceu_sd_old = ceudev->sd;
> +	ceudev->sd = &ceudev->subdevs[i];
> +
> +	/* Make sure we can generate output image formats. */
> +	ret = ceu_init_formats(ceudev);
> +	if (ret) {
> +		ceudev->sd = 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_sd, core, s_power, 0);
> +	v4l2_subdev_call(ceudev->sd->v4l2_sd, core, s_power, 1);
> +
> +	ceudev->sd_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);
> +
> +	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
> +		return -EINVAL;
> +
> +	return v4l2_subdev_call(ceudev->sd->v4l2_sd, 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_MPLANE)
> +		return -EINVAL;
> +
> +	return v4l2_subdev_call(ceudev->sd->v4l2_sd, 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->sd;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	int ret;
> +
> +	struct v4l2_subdev_frame_size_enum fse = {
> +		.code	= ceu_sd->mbus_fmt.mbus_code,
> +		.index	= fsize->index,
> +		.which	= V4L2_SUBDEV_FORMAT_ACTIVE,
> +	};
> +
> +	ret = v4l2_subdev_call(v4l2_sd, 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);
> +	struct ceu_subdev *ceu_sd = ceudev->sd;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	int ret;
> +
> +	struct v4l2_subdev_frame_interval_enum fie = {
> +		.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(v4l2_sd, 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_mplane	= ceu_enum_fmt_vid_cap,
> +	.vidioc_try_fmt_vid_cap_mplane	= ceu_try_fmt_vid_cap,
> +	.vidioc_s_fmt_vid_cap_mplane	= ceu_s_fmt_vid_cap,
> +	.vidioc_g_fmt_vid_cap_mplane	= ceu_g_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 *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(asd);
> +
> +	if (video_is_registered(&ceudev->vdev)) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Video device registered before this sub-device.\n");
> +		return -EBUSY;
> +	}
> +
> +	/* Assign subdevices in the order they appear */
> +	ceu_sd->v4l2_sd = v4l2_sd;
> +	ceudev->num_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 */
> +	q->type			= V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> +	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;
> +
> +	/*
> +	 * Make sure at least one sensor is primary and use it to initialize
> +	 * ceu formats
> +	 */
> +	if (!ceudev->sd) {
> +		ceudev->sd = &ceudev->subdevs[0];
> +		ceudev->sd_index = 0;
> +	}
> +
> +	v4l2_sd = ceudev->sd->v4l2_sd;
> +
> +	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	= v4l2_sd->ctrl_handler;
> +	vdev->fops		= &ceu_fops;
> +	vdev->ioctl_ops		= &ceu_ioctl_ops;
> +	vdev->release		= ceu_vdev_release;
> +	vdev->device_caps	= V4L2_CAP_VIDEO_CAPTURE_MPLANE |
> +				  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;
> +}
> +
> +/**
> + * ceu_parse_init_sd() - Initialize CEU subdevices and async_subdevs in
> + *			 ceu device. Both DT and platform data parsing use
> + *			 this routine.
> + *
> + * @return 0 for success, -ENOMEM for failure.
> + */
> +static int ceu_parse_init_sd(struct ceu_device *ceudev, unsigned int n_sd)
> +{
> +	/* Reserve memory for 'n_sd' ceu_subdev descriptors */
> +	ceudev->subdevs = devm_kcalloc(ceudev->dev, n_sd,
> +				       sizeof(*ceudev->subdevs), GFP_KERNEL);
> +	if (!ceudev->subdevs)
> +		return -ENOMEM;
> +
> +	/*
> +	 * Reserve memory for 'n_sd' pointers to async_subdevices.
> +	 * ceudev->asds members will point to &ceu_subdev.asd
> +	 */
> +	ceudev->asds = devm_kcalloc(ceudev->dev, n_sd,
> +				    sizeof(*ceudev->asds), GFP_KERNEL);
> +	if (!ceudev->asds)
> +		return -ENOMEM;
> +
> +	ceudev->sd = NULL;
> +	ceudev->sd_index = 0;
> +	ceudev->num_sd = 0;
> +
> +	return 0;
> +}
> +
> +/**
> + * ceu_parse_platform_data() - Initialize async_subdevices using platform
> + *			       device provided data.
> + */
> +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;
> +	int ret;
> +
> +	ret = ceu_parse_init_sd(ceudev, info->num_subdevs);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < info->num_subdevs; i++) {
> +		/* Setup the ceu subdevice and the async subdevice */
> +		async_sd = &info->subdevs[i];
> +		ceu_sd = &ceudev->subdevs[i];
> +
> +		memset(&ceu_sd->asd, 0, sizeof(ceu_sd->asd));
> +		INIT_LIST_HEAD(&ceu_sd->asd.list);
> +
> +		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;
> +
> +		ceudev->asds[i] = &ceu_sd->asd;
> +	}
> +
> +	return info->num_subdevs;
> +}
> +
> +/**
> + * ceu_parse_dt() - Initialize async_subdevs parsing device tree graph
> + */
> +static int ceu_parse_dt(struct ceu_device *ceudev)
> +{
> +	struct device_node *of = ceudev->dev->of_node;
> +	struct v4l2_fwnode_endpoint fw_ep;
> +	struct ceu_subdev *ceu_sd;
> +	struct device_node *ep;
> +	unsigned int i;
> +	int num_ep;
> +	int ret;
> +
> +	num_ep = of_graph_get_endpoint_count(of);
> +	if (num_ep <= 0)
> +		return 0;
> +
> +	ret = ceu_parse_init_sd(ceudev, num_ep);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < num_ep; i++) {
> +		ep = of_graph_get_endpoint_by_regs(of, 0, i);
> +		if (!ep) {
> +			v4l2_err(&ceudev->v4l2_dev,
> +				 "No subdevice connected on port %u.\n", i);
> +			ret = -ENODEV;
> +			goto error_put_node;
> +		}
> +
> +		ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &fw_ep);
> +		if (ret) {
> +			v4l2_err(&ceudev->v4l2_dev,
> +				 "Unable to parse endpoint #%u.\n", i);
> +			goto error_put_node;
> +		}
> +
> +		if (fw_ep.bus_type != V4L2_MBUS_PARALLEL) {
> +			v4l2_err(&ceudev->v4l2_dev,
> +				 "Only parallel input supported.\n");
> +			ret = -EINVAL;
> +			goto error_put_node;
> +		}
> +
> +		/* Setup the ceu subdevice and the async subdevice */
> +		ceu_sd = &ceudev->subdevs[i];
> +		memset(&ceu_sd->asd, 0, sizeof(ceu_sd->asd));
> +		INIT_LIST_HEAD(&ceu_sd->asd.list);
> +
> +		ceu_sd->mbus_flags = fw_ep.bus.parallel.flags;
> +		ceu_sd->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
> +		ceu_sd->asd.match.fwnode.fwnode =
> +			fwnode_graph_get_remote_port_parent(
> +					of_fwnode_handle(ep));
> +
> +		ceudev->asds[i] = &ceu_sd->asd;
> +		of_node_put(ep);
> +	}
> +
> +	return num_ep;
> +
> +error_put_node:
> +	of_node_put(ep);
> +	return ret;
> +}
> +
> +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 num_sd;
> +	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);
> +	if (IS_ERR(res))
> +		return PTR_ERR(res);
> +
> +	base = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(base))
> +		return PTR_ERR(base);
> +	ceudev->base = base;
> +
> +	ret = platform_get_irq(pdev, 0);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to get irq: %d\n", ret);
> +		return ret;
> +	}
> +	irq = ret;
> +
> +	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;
> +	}
> +
> +	pm_suspend_ignore_children(dev, true);
> +	pm_runtime_enable(dev);
> +
> +	ret = v4l2_device_register(dev, &ceudev->v4l2_dev);
> +	if (ret)
> +		goto error_pm_disable;
> +
> +	if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
> +		num_sd = ceu_parse_dt(ceudev);
> +	} else if (dev->platform_data) {
> +		num_sd = ceu_parse_platform_data(ceudev, dev->platform_data);
> +	} else {
> +		dev_err(dev, "CEU platform data not set and no OF support\n");
> +		ret = -EINVAL;
> +		goto error_v4l2_unregister;
> +	}
> +
> +	if (num_sd < 0) {
> +		ret = num_sd;
> +		goto error_v4l2_unregister;
> +	} else if (num_sd == 0)
> +		return 0;
> +
> +	ceudev->notifier.v4l2_dev	= &ceudev->v4l2_dev;
> +	ceudev->notifier.subdevs	= ceudev->asds;
> +	ceudev->notifier.num_subdevs	= num_sd;
> +	ceudev->notifier.bound		= ceu_sensor_bound;
> +	ceudev->notifier.complete	= ceu_sensor_complete;
> +	ret = v4l2_async_notifier_register(&ceudev->v4l2_dev,
> +					   &ceudev->notifier);
> +	if (ret)
> +		goto error_v4l2_unregister_notifier;
> +
> +	dev_info(dev, "Renesas Capture Engine Unit\n");
> +
> +	return 0;
> +
> +error_v4l2_unregister_notifier:
> +	v4l2_async_notifier_unregister(&ceudev->notifier);
> +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[] = {
> +	{ .compatible = "renesas,renesas-ceu" },

Even if you add support for new hardware, shouldn't you maintain support
for renesas,sh-mobile-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");

-- 
Sakari Ailus
e-mail: sakari.ailus@iki.fi

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

* Re: [PATCH v1 01/10] dt-bindings: media: Add Renesas CEU bindings
  2017-11-15 10:55 ` [PATCH v1 01/10] dt-bindings: media: Add Renesas CEU bindings Jacopo Mondi
  2017-11-15 11:32   ` Kieran Bingham
  2017-11-15 12:33   ` Sakari Ailus
@ 2017-11-15 13:07   ` Geert Uytterhoeven
  2017-11-15 18:15     ` jacopo mondi
  2 siblings, 1 reply; 56+ messages in thread
From: Geert Uytterhoeven @ 2017-11-15 13:07 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Laurent Pinchart, Magnus Damm, Mauro Carvalho Chehab,
	Hans Verkuil, Linux-Renesas, Linux Media Mailing List,
	Linux-sh list, linux-kernel, Rob Herring, Mark Rutland,
	devicetree

Hi Jacopo,

CC devicetree folks

On Wed, Nov 15, 2017 at 11:55 AM, Jacopo Mondi
<jacopo+renesas@jmondi.org> wrote:
> Add bindings documentation for Renesas Capture Engine Unit (CEU).
>
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  .../devicetree/bindings/media/renesas,ceu.txt      | 87 ++++++++++++++++++++++
>  1 file changed, 87 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/renesas,ceu.txt
>
> diff --git a/Documentation/devicetree/bindings/media/renesas,ceu.txt b/Documentation/devicetree/bindings/media/renesas,ceu.txt
> new file mode 100644
> index 0000000..a88e9cb
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/renesas,ceu.txt
> @@ -0,0 +1,87 @@
> +Renesas Capture Engine Unit (CEU)
> +----------------------------------------------
> +
> +The Capture Engine Unit is the image capture interface found on Renesas
> +RZ chip series and on SH Mobile ones.
> +
> +The interface supports a single parallel input with up 8/16bits data bus width.

... with data bus widths up to 8/16 bits?

> +
> +Required properties:
> +- compatible
> +       Must be "renesas,renesas-ceu".

The double "renesas" part looks odd to me. What about "renesas,ceu"?
Shouldn't you add SoC-specific compatible values like "renesas,r7s72100-ceu",
too?

> +- reg
> +       Physical address base and size.
> +- interrupts
> +       The interrupt line number.

interrupt specifier

> +- pinctrl-names, pinctrl-0
> +       phandle of pin controller sub-node configuring pins for CEU operations.
> +
> +CEU supports a single parallel input and should contain a single 'port' subnode
> +with a single 'endpoint'. Optional endpoint properties applicable to parallel
> +input bus are described in "video-interfaces.txt".
> +
> +Example:
> +
> +The example describes the connection between the Capture Engine Unit and a

... an

> +OV7670 image sensor sitting on bus i2c1 with an on-board 24Mhz clock.
> +
> +ceu: ceu@e8210000 {
> +       reg = <0xe8210000 0x209c>;
> +       compatible = "renesas,renesas-ceu";
> +       interrupts = <GIC_SPI 332 IRQ_TYPE_LEVEL_HIGH>;
> +       pinctrl-names = "default";
> +       pinctrl-0 = <&vio_pins>;
> +
> +       status = "okay";
> +
> +       port {
> +               ceu_in: endpoint {
> +                       remote-endpoint = <&ov7670_out>;
> +
> +                       bus-width = <8>;
> +                       hsync-active = <1>;
> +                       vsync-active = <1>;
> +                       pclk-sample = <1>;
> +                       data-active = <1>;
> +               };
> +       };
> +};
> +
> +i2c1: i2c@fcfee400 {
> +       pinctrl-names = "default";
> +       pinctrl-0 = <&i2c1_pins>;
> +
> +       status = "okay";
> +       clock-frequency = <100000>;
> +
> +       ov7670: camera@21 {
> +               compatible = "ovti,ov7670";
> +               reg = <0x21>;
> +
> +               pinctrl-names = "default";
> +               pinctrl-0 = <&vio_pins>;
> +
> +               reset-gpios = <&port3 11 GPIO_ACTIVE_LOW>;
> +               powerdown-gpios = <&port3 12 GPIO_ACTIVE_HIGH>;
> +
> +               clocks = <&xclk>;
> +               clock-names = "xclk";
> +
> +               xclk: fixed_clk {
> +                       compatible = "fixed-clock";
> +                       #clock-cells = <0>;
> +                       clock-frequency = <24000000>;
> +               };
> +
> +               port {
> +                       ov7670_out: endpoint {
> +                               remote-endpoint = <&ceu_in>;
> +
> +                               bus-width = <8>;
> +                               hsync-active = <1>;
> +                               vsync-active = <1>;
> +                               pclk-sample = <1>;
> +                               data-active = <1>;
> +                       };
> +               };
> +       };
> --
> 2.7.4

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds

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

* Re: [PATCH v1 06/10] sh: sh7722: Rename CEU clock
  2017-11-15 10:55 ` [PATCH v1 06/10] sh: sh7722: Rename CEU clock Jacopo Mondi
@ 2017-11-15 13:13   ` Geert Uytterhoeven
  2017-11-17  9:15     ` jacopo mondi
  0 siblings, 1 reply; 56+ messages in thread
From: Geert Uytterhoeven @ 2017-11-15 13:13 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Laurent Pinchart, Magnus Damm, Mauro Carvalho Chehab,
	Hans Verkuil, Linux-Renesas, Linux Media Mailing List,
	Linux-sh list, linux-kernel

Hi Jacopo,

On Wed, Nov 15, 2017 at 11:55 AM, Jacopo Mondi
<jacopo+renesas@jmondi.org> wrote:
> Rename CEU clock to match the new platform driver name used in Migo-R.
>
> There are no other sh7722 based devices Migo-R apart, so we can safely
> rename this.
>
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  arch/sh/kernel/cpu/sh4a/clock-sh7722.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/arch/sh/kernel/cpu/sh4a/clock-sh7722.c b/arch/sh/kernel/cpu/sh4a/clock-sh7722.c
> index 8f07a1a..d85091e 100644
> --- a/arch/sh/kernel/cpu/sh4a/clock-sh7722.c
> +++ b/arch/sh/kernel/cpu/sh4a/clock-sh7722.c
> @@ -223,7 +223,7 @@ static struct clk_lookup lookups[] = {
>         CLKDEV_DEV_ID("sh-vou.0", &mstp_clks[HWBLK_VOU]),
>         CLKDEV_CON_ID("jpu0", &mstp_clks[HWBLK_JPU]),
>         CLKDEV_CON_ID("beu0", &mstp_clks[HWBLK_BEU]),
> -       CLKDEV_DEV_ID("sh_mobile_ceu.0", &mstp_clks[HWBLK_CEU]),
> +       CLKDEV_DEV_ID("renesas-ceu.0", &mstp_clks[HWBLK_CEU]),
>         CLKDEV_CON_ID("veu0", &mstp_clks[HWBLK_VEU]),
>         CLKDEV_CON_ID("vpu0", &mstp_clks[HWBLK_VPU]),
>         CLKDEV_DEV_ID("sh_mobile_lcdc_fb.0", &mstp_clks[HWBLK_LCDC]),

Shouldn't this be merged with "[PATCH v1 05/10] arch: sh: migor: Use new
renesas-ceu camera driver", to avoid breaking bisection?

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-11-15 12:45   ` Sakari Ailus
@ 2017-11-15 14:25     ` jacopo mondi
  2017-11-17  0:36       ` Sakari Ailus
  0 siblings, 1 reply; 56+ messages in thread
From: jacopo mondi @ 2017-11-15 14:25 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Jacopo Mondi, laurent.pinchart, magnus.damm, geert, mchehab,
	hverkuil, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Sakari,
   thanks for review!

On Wed, Nov 15, 2017 at 02:45:51PM +0200, Sakari Ailus wrote:
> Hi Jacopo,
>
> Could you remove the original driver and send the patch using git
> send-email -C ? That way a single patch would address converting it to a
> proper V4L2 driver as well as move it to the correct location. The changes
> would be easier to review that way since then, well, it'd be easier to see
> the changes. :-)

Actually I prefer not to remove the existing driver at the moment. See
the cover letter for reasons why not to do so right now...

Also, there's not that much code from the old driver in here, surely
less than the default 50% -C and -M options of 'git format-patch' use
as a threshold for detecting copies iirc..

I would prefer this to be reviewed as new driver, I know it's a bit
more painful, but irq handler and a couple of other routines apart,
there's not that much code shared between the two...

>
> The same goes for the two V4L2 SoC camera sensor / video decoder drivers at
> the end of the set.
>

Also in this case I prefer not to remove existing code, as long as
there are platforms using it..

> On Wed, Nov 15, 2017 at 11:55:56AM +0100, Jacopo Mondi wrote:
> > Add driver for Renesas Capture Engine Unit (CEU).
> >
> > The CEU interface supports capturing 'data' (YUV422) and 'images'
> > (NV[12|21|16|61]).
> >
> > This driver aims to replace the soc_camera based sh_mobile_ceu one.
> >
> > Tested with ov7670 camera sensor, providing YUYV_2X8 data on Renesas RZ
> > platform GR-Peach.
> >
> > Tested with ov7725 camera sensor on SH4 platform Migo-R.
>
> Nice!
>
> >
> > Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> > ---
> > +#include <linux/completion.h>
>
> Do you need this header? There would seem some that I wouldn't expect to be
> needed below, such as linux/init.h.

It's probably a leftover, I'll remove it...

[snip]
>
> > +#if IS_ENABLED(CONFIG_OF)
> > +static const struct of_device_id ceu_of_match[] = {
> > +	{ .compatible = "renesas,renesas-ceu" },
>
> Even if you add support for new hardware, shouldn't you maintain support
> for renesas,sh-mobile-ceu?
>

As you noticed already, the old driver did not support OF, so there
are no compatibility issues here

Thanks
   j

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

* Re: [PATCH v1 01/10] dt-bindings: media: Add Renesas CEU bindings
  2017-11-15 13:07   ` Geert Uytterhoeven
@ 2017-11-15 18:15     ` jacopo mondi
  2017-11-15 18:39       ` Geert Uytterhoeven
  0 siblings, 1 reply; 56+ messages in thread
From: jacopo mondi @ 2017-11-15 18:15 UTC (permalink / raw)
  To: Geert Uytterhoeven
  Cc: Jacopo Mondi, Laurent Pinchart, Magnus Damm,
	Mauro Carvalho Chehab, Hans Verkuil, Linux-Renesas,
	Linux Media Mailing List, Linux-sh list, linux-kernel,
	Rob Herring, Mark Rutland, devicetree

Hi Geert,

On Wed, Nov 15, 2017 at 02:07:31PM +0100, Geert Uytterhoeven wrote:
> Hi Jacopo,
>
> CC devicetree folks

Yeah, sorry I forgot them. Sorry about this and thanks for adding the
address back!

>
> On Wed, Nov 15, 2017 at 11:55 AM, Jacopo Mondi
> <jacopo+renesas@jmondi.org> wrote:
> > Add bindings documentation for Renesas Capture Engine Unit (CEU).
> >
> > Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> > ---
> >  .../devicetree/bindings/media/renesas,ceu.txt      | 87 ++++++++++++++++++++++
> >  1 file changed, 87 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/media/renesas,ceu.txt
> >
> > diff --git a/Documentation/devicetree/bindings/media/renesas,ceu.txt b/Documentation/devicetree/bindings/media/renesas,ceu.txt
> > new file mode 100644
> > index 0000000..a88e9cb
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/media/renesas,ceu.txt
> > @@ -0,0 +1,87 @@
> > +Renesas Capture Engine Unit (CEU)
> > +----------------------------------------------
> > +
> > +The Capture Engine Unit is the image capture interface found on Renesas
> > +RZ chip series and on SH Mobile ones.
> > +
> > +The interface supports a single parallel input with up 8/16bits data bus width.
>
> ... with data bus widths up to 8/16 bits?
>
> > +
> > +Required properties:
> > +- compatible
> > +       Must be "renesas,renesas-ceu".
>
> The double "renesas" part looks odd to me. What about "renesas,ceu"?

I'm totally open for better "compatible" strings here, so yeah, let's
got for the shorter one you proposed...

> Shouldn't you add SoC-specific compatible values like "renesas,r7s72100-ceu",
> too?

Well, I actually have no SoC-specific data in the driver, so I don't
need SoC specific "compatible" values. But if it's a good practice
to have them anyway, I will add those in next spin..
>
> > +- reg
> > +       Physical address base and size.
> > +- interrupts
> > +       The interrupt line number.
>
> interrupt specifier

Yeah, it's not just the line number...

>

[snip]

> > +i2c1: i2c@fcfee400 {
> > +       pinctrl-names = "default";
> > +       pinctrl-0 = <&i2c1_pins>;
> > +
> > +       status = "okay";
> > +       clock-frequency = <100000>;
> > +
> > +       ov7670: camera@21 {
> > +               compatible = "ovti,ov7670";
> > +               reg = <0x21>;
> > +
> > +               pinctrl-names = "default";
> > +               pinctrl-0 = <&vio_pins>;
> > +
> > +               reset-gpios = <&port3 11 GPIO_ACTIVE_LOW>;
> > +               powerdown-gpios = <&port3 12 GPIO_ACTIVE_HIGH>;
> > +
> > +               clocks = <&xclk>;
> > +               clock-names = "xclk";
> > +
> > +               xclk: fixed_clk {
> > +                       compatible = "fixed-clock";
> > +                       #clock-cells = <0>;
> > +                       clock-frequency = <24000000>;
> > +               };

As Sakari pointed out in his review, this fixed clock is a detail
specific to the sensor used in the example (ov7670). For sake of
simplicity I can remove it.

Thanks
  j

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

* Re: [PATCH v1 01/10] dt-bindings: media: Add Renesas CEU bindings
  2017-11-15 18:15     ` jacopo mondi
@ 2017-11-15 18:39       ` Geert Uytterhoeven
  0 siblings, 0 replies; 56+ messages in thread
From: Geert Uytterhoeven @ 2017-11-15 18:39 UTC (permalink / raw)
  To: jacopo mondi
  Cc: Jacopo Mondi, Laurent Pinchart, Magnus Damm,
	Mauro Carvalho Chehab, Hans Verkuil, Linux-Renesas,
	Linux Media Mailing List, Linux-sh list, linux-kernel,
	Rob Herring, Mark Rutland, devicetree

Hi Jacopo,

On Wed, Nov 15, 2017 at 7:15 PM, jacopo mondi <jacopo@jmondi.org> wrote:
> On Wed, Nov 15, 2017 at 02:07:31PM +0100, Geert Uytterhoeven wrote:
>> On Wed, Nov 15, 2017 at 11:55 AM, Jacopo Mondi
>> <jacopo+renesas@jmondi.org> wrote:
>> > --- /dev/null
>> > +++ b/Documentation/devicetree/bindings/media/renesas,ceu.txt
>> > @@ -0,0 +1,87 @@
>> > +Renesas Capture Engine Unit (CEU)
>> > +----------------------------------------------
>> > +
>> > +The Capture Engine Unit is the image capture interface found on Renesas
>> > +RZ chip series and on SH Mobile ones.
>> > +
>> > +The interface supports a single parallel input with up 8/16bits data bus width.
>>
>> ... with data bus widths up to 8/16 bits?
>>
>> > +
>> > +Required properties:
>> > +- compatible
>> > +       Must be "renesas,renesas-ceu".
>>
>> The double "renesas" part looks odd to me. What about "renesas,ceu"?
>
> I'm totally open for better "compatible" strings here, so yeah, let's
> got for the shorter one you proposed...
>
>> Shouldn't you add SoC-specific compatible values like "renesas,r7s72100-ceu",
>> too?
>
> Well, I actually have no SoC-specific data in the driver, so I don't
> need SoC specific "compatible" values. But if it's a good practice
> to have them anyway, I will add those in next spin..

You don't necessarily need them in the driver, but in the bindings and DTS,
just in case a difference is discovered later.

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-11-15 14:25     ` jacopo mondi
@ 2017-11-17  0:36       ` Sakari Ailus
  2017-11-17  9:33         ` jacopo mondi
  2017-12-11 15:04         ` Laurent Pinchart
  0 siblings, 2 replies; 56+ messages in thread
From: Sakari Ailus @ 2017-11-17  0:36 UTC (permalink / raw)
  To: jacopo mondi
  Cc: Jacopo Mondi, laurent.pinchart, magnus.damm, geert, mchehab,
	hverkuil, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Jacopo,

On Wed, Nov 15, 2017 at 03:25:11PM +0100, jacopo mondi wrote:
> Hi Sakari,
>    thanks for review!

You're welcome!

> On Wed, Nov 15, 2017 at 02:45:51PM +0200, Sakari Ailus wrote:
> > Hi Jacopo,
> >
> > Could you remove the original driver and send the patch using git
> > send-email -C ? That way a single patch would address converting it to a
> > proper V4L2 driver as well as move it to the correct location. The changes
> > would be easier to review that way since then, well, it'd be easier to see
> > the changes. :-)
> 
> Actually I prefer not to remove the existing driver at the moment. See
> the cover letter for reasons why not to do so right now...

So it's about testing mostly? Does someone (possibly you) have those boards
to test? I'd like to see this patchset to remove that last remaining SoC
camera bridge driver. :-)

> 
> Also, there's not that much code from the old driver in here, surely
> less than the default 50% -C and -M options of 'git format-patch' use
> as a threshold for detecting copies iirc..

Oh, if that's so, then makes sense to review it as a new driver.

> 
> I would prefer this to be reviewed as new driver, I know it's a bit
> more painful, but irq handler and a couple of other routines apart,
> there's not that much code shared between the two...
> 
> >
> > The same goes for the two V4L2 SoC camera sensor / video decoder drivers at
> > the end of the set.
> >
> 
> Also in this case I prefer not to remove existing code, as long as
> there are platforms using it..

Couldn't they use this driver instead?

> 
> > On Wed, Nov 15, 2017 at 11:55:56AM +0100, Jacopo Mondi wrote:
> > > Add driver for Renesas Capture Engine Unit (CEU).
> > >
> > > The CEU interface supports capturing 'data' (YUV422) and 'images'
> > > (NV[12|21|16|61]).
> > >
> > > This driver aims to replace the soc_camera based sh_mobile_ceu one.
> > >
> > > Tested with ov7670 camera sensor, providing YUYV_2X8 data on Renesas RZ
> > > platform GR-Peach.
> > >
> > > Tested with ov7725 camera sensor on SH4 platform Migo-R.
> >
> > Nice!
> >
> > >
> > > Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> > > ---
> > > +#include <linux/completion.h>
> >
> > Do you need this header? There would seem some that I wouldn't expect to be
> > needed below, such as linux/init.h.
> 
> It's probably a leftover, I'll remove it...
> 
> [snip]
> >
> > > +#if IS_ENABLED(CONFIG_OF)
> > > +static const struct of_device_id ceu_of_match[] = {
> > > +	{ .compatible = "renesas,renesas-ceu" },
> >
> > Even if you add support for new hardware, shouldn't you maintain support
> > for renesas,sh-mobile-ceu?
> >
> 
> As you noticed already, the old driver did not support OF, so there
> are no compatibility issues here

Yeah, I realised that only after reviewing this patch.

It'd be Super-cool if someone did the DT conversion. Perhaps Laurent? ;-)

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi

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

* Re: [PATCH v1 08/10] media: i2c: ov772x: Remove soc_camera dependencies
  2017-11-15 10:56 ` [PATCH v1 08/10] media: i2c: ov772x: Remove soc_camera dependencies Jacopo Mondi
@ 2017-11-17  0:43   ` Sakari Ailus
  2017-11-17  9:14     ` jacopo mondi
  2017-12-11 14:47   ` Laurent Pinchart
  1 sibling, 1 reply; 56+ messages in thread
From: Sakari Ailus @ 2017-11-17  0:43 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Jacopo,

On Wed, Nov 15, 2017 at 11:56:01AM +0100, Jacopo Mondi wrote:
> Remove soc_camera framework dependencies from ov772x sensor driver.
> - Handle clock directly
> - Register async subdevice
> - Add platform specific enable/disable functions
> - Adjust build system
> 
> This commit does not remove the original soc_camera based driver.
> 
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  drivers/media/i2c/Kconfig  | 12 +++++++
>  drivers/media/i2c/Makefile |  1 +
>  drivers/media/i2c/ov772x.c | 88 +++++++++++++++++++++++++++++++---------------
>  include/media/i2c/ov772x.h |  3 ++
>  4 files changed, 76 insertions(+), 28 deletions(-)
> 
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index 9415389..ff251ce 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -629,6 +629,18 @@ config VIDEO_OV5670
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called ov5670.
> 
> +config VIDEO_OV772X
> +	tristate "OmniVision OV772x sensor support"
> +	depends on I2C && VIDEO_V4L2
> +	depends on MEDIA_CAMERA_SUPPORT
> +	---help---
> +	  This is a Video4Linux2 sensor-level driver for the OmniVision
> +	  OV772x camera.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called ov772x.
> +
> +
>  config VIDEO_OV7640
>  	tristate "OmniVision OV7640 sensor support"
>  	depends on I2C && VIDEO_V4L2
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index f104650..b2459a1 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -66,6 +66,7 @@ obj-$(CONFIG_VIDEO_OV5645) += ov5645.o
>  obj-$(CONFIG_VIDEO_OV5647) += ov5647.o
>  obj-$(CONFIG_VIDEO_OV5670) += ov5670.o
>  obj-$(CONFIG_VIDEO_OV6650) += ov6650.o
> +obj-$(CONFIG_VIDEO_OV772X) += ov772x.o
>  obj-$(CONFIG_VIDEO_OV7640) += ov7640.o
>  obj-$(CONFIG_VIDEO_OV7670) += ov7670.o
>  obj-$(CONFIG_VIDEO_OV9650) += ov9650.o
> diff --git a/drivers/media/i2c/ov772x.c b/drivers/media/i2c/ov772x.c
> index 8063835..9be7e4e 100644
> --- a/drivers/media/i2c/ov772x.c
> +++ b/drivers/media/i2c/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>

Alphabetical order would be nice.

>  #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;

What does this do, enable the regulator?

> +	}
> +
> +	/*  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)
> @@ -1000,14 +1024,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;
>  }
> @@ -1038,12 +1058,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;
>  	}
> 
> @@ -1059,7 +1078,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);
> @@ -1073,21 +1092,33 @@ 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;
>  }
> @@ -1096,7 +1127,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
> 

-- 
Sakari Ailus
e-mail: sakari.ailus@iki.fi

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

* Re: [PATCH v1 08/10] media: i2c: ov772x: Remove soc_camera dependencies
  2017-11-17  0:43   ` Sakari Ailus
@ 2017-11-17  9:14     ` jacopo mondi
  2017-11-25 16:04       ` Sakari Ailus
  0 siblings, 1 reply; 56+ messages in thread
From: jacopo mondi @ 2017-11-17  9:14 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Jacopo Mondi, laurent.pinchart, magnus.damm, geert, mchehab,
	hverkuil, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Sakari!

On Fri, Nov 17, 2017 at 02:43:15AM +0200, Sakari Ailus wrote:
> Hi Jacopo,
>
> On Wed, Nov 15, 2017 at 11:56:01AM +0100, Jacopo Mondi wrote:
> >

[snip]

> > +#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>
>
> Alphabetical order would be nice.

ups!

>
> >  #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;
>
> What does this do, enable the regulator?

Well, it depends on what function the platform code stores in
'platform_enable' pointer, doesn't it?

As you can see in [05/10] of this series, for Migo-R it's not about
a regulator, but switching between the two available video inputs
(OV7725 and TW9910) toggling their 'enable' pins.

Thanks
   j

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

* Re: [PATCH v1 06/10] sh: sh7722: Rename CEU clock
  2017-11-15 13:13   ` Geert Uytterhoeven
@ 2017-11-17  9:15     ` jacopo mondi
  0 siblings, 0 replies; 56+ messages in thread
From: jacopo mondi @ 2017-11-17  9:15 UTC (permalink / raw)
  To: Geert Uytterhoeven
  Cc: Jacopo Mondi, Laurent Pinchart, Magnus Damm,
	Mauro Carvalho Chehab, Hans Verkuil, Linux-Renesas,
	Linux Media Mailing List, Linux-sh list, linux-kernel

Hi Geert,

On Wed, Nov 15, 2017 at 02:13:43PM +0100, Geert Uytterhoeven wrote:
> Hi Jacopo,
>
> On Wed, Nov 15, 2017 at 11:55 AM, Jacopo Mondi
> <jacopo+renesas@jmondi.org> wrote:
> > Rename CEU clock to match the new platform driver name used in Migo-R.
> >
> > There are no other sh7722 based devices Migo-R apart, so we can safely
> > rename this.
> >
> > Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> > ---
> >  arch/sh/kernel/cpu/sh4a/clock-sh7722.c | 2 +-
> >  1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/arch/sh/kernel/cpu/sh4a/clock-sh7722.c b/arch/sh/kernel/cpu/sh4a/clock-sh7722.c
> > index 8f07a1a..d85091e 100644
> > --- a/arch/sh/kernel/cpu/sh4a/clock-sh7722.c
> > +++ b/arch/sh/kernel/cpu/sh4a/clock-sh7722.c
> > @@ -223,7 +223,7 @@ static struct clk_lookup lookups[] = {
> >         CLKDEV_DEV_ID("sh-vou.0", &mstp_clks[HWBLK_VOU]),
> >         CLKDEV_CON_ID("jpu0", &mstp_clks[HWBLK_JPU]),
> >         CLKDEV_CON_ID("beu0", &mstp_clks[HWBLK_BEU]),
> > -       CLKDEV_DEV_ID("sh_mobile_ceu.0", &mstp_clks[HWBLK_CEU]),
> > +       CLKDEV_DEV_ID("renesas-ceu.0", &mstp_clks[HWBLK_CEU]),
> >         CLKDEV_CON_ID("veu0", &mstp_clks[HWBLK_VEU]),
> >         CLKDEV_CON_ID("vpu0", &mstp_clks[HWBLK_VPU]),
> >         CLKDEV_DEV_ID("sh_mobile_lcdc_fb.0", &mstp_clks[HWBLK_LCDC]),
>
> Shouldn't this be merged with "[PATCH v1 05/10] arch: sh: migor: Use new
> renesas-ceu camera driver", to avoid breaking bisection?

That's a good idea. I will merge these two commits in v2.

Thanks
   j

>
> Gr{oetje,eeting}s,
>
>                         Geert
>
> --
> Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
>
> In personal conversations with technical people, I call myself a hacker. But
> when I'm talking to journalists I just say "programmer" or something like that.
>                                 -- Linus Torvalds

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-11-17  0:36       ` Sakari Ailus
@ 2017-11-17  9:33         ` jacopo mondi
  2017-11-25 15:56           ` Sakari Ailus
  2017-12-11 15:04         ` Laurent Pinchart
  1 sibling, 1 reply; 56+ messages in thread
From: jacopo mondi @ 2017-11-17  9:33 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Jacopo Mondi, laurent.pinchart, magnus.damm, geert, mchehab,
	hverkuil, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Sakari!

On Fri, Nov 17, 2017 at 02:36:51AM +0200, Sakari Ailus wrote:
> Hi Jacopo,
>
> On Wed, Nov 15, 2017 at 03:25:11PM +0100, jacopo mondi wrote:
> > Hi Sakari,
> >    thanks for review!
>
> You're welcome!
>
> > On Wed, Nov 15, 2017 at 02:45:51PM +0200, Sakari Ailus wrote:
> > > Hi Jacopo,
> > >
> > > Could you remove the original driver and send the patch using git
> > > send-email -C ? That way a single patch would address converting it to a
> > > proper V4L2 driver as well as move it to the correct location. The changes
> > > would be easier to review that way since then, well, it'd be easier to see
> > > the changes. :-)
> >
> > Actually I prefer not to remove the existing driver at the moment. See
> > the cover letter for reasons why not to do so right now...
>
> So it's about testing mostly? Does someone (possibly you) have those boards
> to test? I'd like to see this patchset to remove that last remaining SoC
> camera bridge driver. :-)

Well, we agreed that for most of those platforms, compile testing it
would be enough (let's believe in "if it compiles, it works"). I
personally don't have access to those boards, and frankly I'm not even
sure there are many of them around these days (I guess most of them
are not even produced anymore).

>
> >
> > Also, there's not that much code from the old driver in here, surely
> > less than the default 50% -C and -M options of 'git format-patch' use
> > as a threshold for detecting copies iirc..
>
> Oh, if that's so, then makes sense to review it as a new driver.

thanks :)

>
> >
> > > The same goes for the two V4L2 SoC camera sensor / video decoder drivers at
> > > the end of the set.
> > >
> >
> > Also in this case I prefer not to remove existing code, as long as
> > there are platforms using it..
>
> Couldn't they use this driver instead?

Oh, they will eventually, I hope :)

I would like to make sure we're all on the same page with this. My
preference would be:

1) Have renesas-ceu.c driver merged with Migo-R ported to use this new
driver as an 'example'.
2) Do not remove any of the existing soc_camera code at this point
3) Port all other 4 SH users of sh_mobile_ceu_camera to use the now
merged renesas-ceu driver
4) Remove sh_mobile_ceu_camera and soc_camera sensor drivers whose
only users were those 4 SH boards
5) Remove soc_camera completely. For my understanding there are some
PXA platforms still using soc_camera provided utilities somewhere.
Hans knows better, but we can discuss this once we'll get there.

Let me know if this is ok for everyone.

Thanks
   j

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

* Re: [PATCH v1 04/10] ARM: dts: r7s72100: Add Capture Engine Unit (CEU)
  2017-11-15 10:55 ` [PATCH v1 04/10] ARM: dts: r7s72100: Add Capture Engine Unit (CEU) Jacopo Mondi
@ 2017-11-17 14:22   ` Simon Horman
  2017-11-23  9:41   ` Geert Uytterhoeven
  1 sibling, 0 replies; 56+ messages in thread
From: Simon Horman @ 2017-11-17 14:22 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: laurent.pinchart, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

On Wed, Nov 15, 2017 at 11:55:57AM +0100, Jacopo Mondi wrote:
> Add Capture Engine Unit (CEU) node to device tree.

Other patches in this series (which are not for my tree) appear
to warrant updating. Accordingly I am marking this patch as
"Changes Requested" and am expecting it to be reposted at some point.

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

* Re: [PATCH v1 04/10] ARM: dts: r7s72100: Add Capture Engine Unit (CEU)
  2017-11-15 10:55 ` [PATCH v1 04/10] ARM: dts: r7s72100: Add Capture Engine Unit (CEU) Jacopo Mondi
  2017-11-17 14:22   ` Simon Horman
@ 2017-11-23  9:41   ` Geert Uytterhoeven
  1 sibling, 0 replies; 56+ messages in thread
From: Geert Uytterhoeven @ 2017-11-23  9:41 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Laurent Pinchart, Magnus Damm, Mauro Carvalho Chehab,
	Hans Verkuil, Linux-Renesas, Linux Media Mailing List,
	Linux-sh list, linux-kernel

Hi Jacopo,

On Wed, Nov 15, 2017 at 11:55 AM, Jacopo Mondi
<jacopo+renesas@jmondi.org> wrote:
> Add Capture Engine Unit (CEU) node to device tree.
>
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>

Thanks for your patch!

> --- a/arch/arm/boot/dts/r7s72100.dtsi
> +++ b/arch/arm/boot/dts/r7s72100.dtsi
> @@ -136,8 +136,8 @@
>                         compatible = "renesas,r7s72100-mstp-clocks", "renesas,cpg-mstp-clocks";
>                         reg = <0xfcfe042c 4>;
>                         clocks = <&p0_clk>;

You forgot to add an entry to clocks.
The parent clock of the CEU module clock is b_clk.

> -                       clock-indices = <R7S72100_CLK_RTC>;
> -                       clock-output-names = "rtc";
> +                       clock-indices = <R7S72100_CLK_RTC R7S72100_CLK_CEU>;
> +                       clock-output-names = "rtc", "ceu";

Usually we follow the order from <dt-bindings/clock/r7s72100-clock.h>,
so CEU should come before RTC.

> @@ -666,4 +666,12 @@
>                 power-domains = <&cpg_clocks>;
>                 status = "disabled";
>         };
> +
> +       ceu: ceu@e8210000 {
> +               reg = <0xe8210000 0x209c>;
> +               compatible = "renesas,renesas-ceu";
> +               interrupts = <GIC_SPI 332 IRQ_TYPE_LEVEL_HIGH>;
> +               power-domains = <&cpg_clocks>;

if you describe the device to be part of the CPG clock domain, you should
provide a clocks property:

        clocks = <&mstp6_clks R7S72100_CLK_CEU>;

> +               status = "disabled";
> +       };
>  };

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-11-17  9:33         ` jacopo mondi
@ 2017-11-25 15:56           ` Sakari Ailus
  2017-11-25 18:17             ` jacopo mondi
  0 siblings, 1 reply; 56+ messages in thread
From: Sakari Ailus @ 2017-11-25 15:56 UTC (permalink / raw)
  To: jacopo mondi
  Cc: Jacopo Mondi, laurent.pinchart, magnus.damm, geert, mchehab,
	hverkuil, linux-renesas-soc, linux-media, linux-sh, linux-kernel

On Fri, Nov 17, 2017 at 10:33:55AM +0100, jacopo mondi wrote:
> Hi Sakari!
> 
> On Fri, Nov 17, 2017 at 02:36:51AM +0200, Sakari Ailus wrote:
> > Hi Jacopo,
> >
> > On Wed, Nov 15, 2017 at 03:25:11PM +0100, jacopo mondi wrote:
> > > Hi Sakari,
> > >    thanks for review!
> >
> > You're welcome!
> >
> > > On Wed, Nov 15, 2017 at 02:45:51PM +0200, Sakari Ailus wrote:
> > > > Hi Jacopo,
> > > >
> > > > Could you remove the original driver and send the patch using git
> > > > send-email -C ? That way a single patch would address converting it to a
> > > > proper V4L2 driver as well as move it to the correct location. The changes
> > > > would be easier to review that way since then, well, it'd be easier to see
> > > > the changes. :-)
> > >
> > > Actually I prefer not to remove the existing driver at the moment. See
> > > the cover letter for reasons why not to do so right now...
> >
> > So it's about testing mostly? Does someone (possibly you) have those boards
> > to test? I'd like to see this patchset to remove that last remaining SoC
> > camera bridge driver. :-)
> 
> Well, we agreed that for most of those platforms, compile testing it
> would be enough (let's believe in "if it compiles, it works"). I
> personally don't have access to those boards, and frankly I'm not even
> sure there are many of them around these days (I guess most of them
> are not even produced anymore).
> 
> >
> > >
> > > Also, there's not that much code from the old driver in here, surely
> > > less than the default 50% -C and -M options of 'git format-patch' use
> > > as a threshold for detecting copies iirc..
> >
> > Oh, if that's so, then makes sense to review it as a new driver.
> 
> thanks :)
> 
> >
> > >
> > > > The same goes for the two V4L2 SoC camera sensor / video decoder drivers at
> > > > the end of the set.
> > > >
> > >
> > > Also in this case I prefer not to remove existing code, as long as
> > > there are platforms using it..
> >
> > Couldn't they use this driver instead?
> 
> Oh, they will eventually, I hope :)
> 
> I would like to make sure we're all on the same page with this. My
> preference would be:
> 
> 1) Have renesas-ceu.c driver merged with Migo-R ported to use this new
> driver as an 'example'.
> 2) Do not remove any of the existing soc_camera code at this point
> 3) Port all other 4 SH users of sh_mobile_ceu_camera to use the now
> merged renesas-ceu driver
> 4) Remove sh_mobile_ceu_camera and soc_camera sensor drivers whose
> only users were those 4 SH boards
> 5) Remove soc_camera completely. For my understanding there are some
> PXA platforms still using soc_camera provided utilities somewhere.
> Hans knows better, but we can discuss this once we'll get there.

The first point here is basically done by this patchset and your intent
would be to proceed with the rest, right?

The above seems good; what I wanted to say was that I'd like to avoid
ending up in a permanent situation where some hardware works with the new
driver and some will continue to use the old one.

-- 
Kind regards,

Sakari Ailus
e-mail: sakari.ailus@iki.fi

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

* Re: [PATCH v1 08/10] media: i2c: ov772x: Remove soc_camera dependencies
  2017-11-17  9:14     ` jacopo mondi
@ 2017-11-25 16:04       ` Sakari Ailus
  0 siblings, 0 replies; 56+ messages in thread
From: Sakari Ailus @ 2017-11-25 16:04 UTC (permalink / raw)
  To: jacopo mondi
  Cc: Jacopo Mondi, laurent.pinchart, magnus.damm, geert, mchehab,
	hverkuil, linux-renesas-soc, linux-media, linux-sh, linux-kernel

On Fri, Nov 17, 2017 at 10:14:51AM +0100, jacopo mondi wrote:
> Hi Sakari!
> 
> On Fri, Nov 17, 2017 at 02:43:15AM +0200, Sakari Ailus wrote:
> > Hi Jacopo,
> >
> > On Wed, Nov 15, 2017 at 11:56:01AM +0100, Jacopo Mondi wrote:
> > >
> 
> [snip]
> 
> > > +#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>
> >
> > Alphabetical order would be nice.
> 
> ups!
> 
> >
> > >  #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;
> >
> > What does this do, enable the regulator?
> 
> Well, it depends on what function the platform code stores in
> 'platform_enable' pointer, doesn't it?
> 
> As you can see in [05/10] of this series, for Migo-R it's not about
> a regulator, but switching between the two available video inputs
> (OV7725 and TW9910) toggling their 'enable' pins.

Ok. That's not a very nice design.

Fair enough. I guess it's good to proceed one thing at a time.

If someone has this sensor on a board with DT support, we can use the
regulator framework and just ignore the platform callbacks.

-- 
Sakari Ailus
e-mail: sakari.ailus@iki.fi

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-11-25 15:56           ` Sakari Ailus
@ 2017-11-25 18:17             ` jacopo mondi
  0 siblings, 0 replies; 56+ messages in thread
From: jacopo mondi @ 2017-11-25 18:17 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Jacopo Mondi, laurent.pinchart, magnus.damm, geert, mchehab,
	hverkuil, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Sakari!

On Sat, Nov 25, 2017 at 05:56:14PM +0200, Sakari Ailus wrote:
> On Fri, Nov 17, 2017 at 10:33:55AM +0100, jacopo mondi wrote:
> > Hi Sakari!
> >

[snip]

> > I would like to make sure we're all on the same page with this. My
> > preference would be:
> >
> > 1) Have renesas-ceu.c driver merged with Migo-R ported to use this new
> > driver as an 'example'.
> > 2) Do not remove any of the existing soc_camera code at this point
> > 3) Port all other 4 SH users of sh_mobile_ceu_camera to use the now
> > merged renesas-ceu driver
> > 4) Remove sh_mobile_ceu_camera and soc_camera sensor drivers whose
> > only users were those 4 SH boards
> > 5) Remove soc_camera completely. For my understanding there are some
> > PXA platforms still using soc_camera provided utilities somewhere.
> > Hans knows better, but we can discuss this once we'll get there.
>
> The first point here is basically done by this patchset and your intent
> would be to proceed with the rest, right?

Yep, you're right!

>
> The above seems good; what I wanted to say was that I'd like to avoid
> ending up in a permanent situation where some hardware works with the new
> driver and some will continue to use the old one.

I hope that being the last users of soc_camera, there will be enough
motivations to complete all the above points :)

Thanks
   j

>
> --
> Kind regards,
>
> Sakari Ailus
> e-mail: sakari.ailus@iki.fi

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

* Re: [PATCH v1 01/10] dt-bindings: media: Add Renesas CEU bindings
  2017-11-15 12:33   ` Sakari Ailus
@ 2017-12-11 14:24     ` Laurent Pinchart
  0 siblings, 0 replies; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-11 14:24 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Jacopo Mondi, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hello,

On Wednesday, 15 November 2017 14:33:12 EET Sakari Ailus wrote:
> On Wed, Nov 15, 2017 at 11:55:54AM +0100, Jacopo Mondi wrote:
> > Add bindings documentation for Renesas Capture Engine Unit (CEU).
> > 
> > Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> > ---
> > 
> >  .../devicetree/bindings/media/renesas,ceu.txt      | 87 +++++++++++++++++
> >  1 file changed, 87 insertions(+)
> >  create mode 100644
> >  Documentation/devicetree/bindings/media/renesas,ceu.txt
> > 
> > diff --git a/Documentation/devicetree/bindings/media/renesas,ceu.txt
> > b/Documentation/devicetree/bindings/media/renesas,ceu.txt new file mode
> > 100644
> > index 0000000..a88e9cb
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/media/renesas,ceu.txt
> > @@ -0,0 +1,87 @@
> > +Renesas Capture Engine Unit (CEU)
> > +----------------------------------------------
> > +
> > +The Capture Engine Unit is the image capture interface found on Renesas
> > +RZ chip series and on SH Mobile ones.
> > +
> > +The interface supports a single parallel input with up 8/16bits data bus
> > width.
> > +
> > +Required properties:
> > +- compatible
> > +	Must be "renesas,renesas-ceu".
> > +- reg
> > +	Physical address base and size.
> > +- interrupts
> > +	The interrupt line number.
> > +- pinctrl-names, pinctrl-0
> > +	phandle of pin controller sub-node configuring pins for CEU operations.
> > +
> > +CEU supports a single parallel input and should contain a single 'port'
> > subnode
> > +with a single 'endpoint'. Optional endpoint properties applicable to
> > parallel
> > +input bus are described in "video-interfaces.txt".
> 
> Could you list which ones they are? For someone not familiar with the
> parallel bus this might not be obvious; also not all hardware can make use
> of every one of these properties.

Agreed, you should list the properties here and reference video-interfaces.txt 
for the detailed description.

> > +
> > +Example:
> > +
> > +The example describes the connection between the Capture Engine Unit and
> > a
> > +OV7670 image sensor sitting on bus i2c1 with an on-board 24Mhz clock.
> > +
> > +ceu: ceu@e8210000 {
> > +	reg = <0xe8210000 0x209c>;
> > +	compatible = "renesas,renesas-ceu";
> > +	interrupts = <GIC_SPI 332 IRQ_TYPE_LEVEL_HIGH>;
> > +	pinctrl-names = "default";
> > +	pinctrl-0 = <&vio_pins>;
> > +
> > +	status = "okay";
> > +
> > +	port {
> > +		ceu_in: endpoint {
> > +			remote-endpoint = <&ov7670_out>;
> > +
> > +			bus-width = <8>;
> > +			hsync-active = <1>;
> > +			vsync-active = <1>;
> > +			pclk-sample = <1>;
> > +			data-active = <1>;
> > +		};
> > +	};
> > +};
> > +
> > +i2c1: i2c@fcfee400 {
> > +	pinctrl-names = "default";
> > +	pinctrl-0 = <&i2c1_pins>;
> > +
> > +	status = "okay";
> > +	clock-frequency = <100000>;
> > +
> > +	ov7670: camera@21 {
> > +		compatible = "ovti,ov7670";
> > +		reg = <0x21>;
> > +
> > +		pinctrl-names = "default";
> > +		pinctrl-0 = <&vio_pins>;
> > +
> > +		reset-gpios = <&port3 11 GPIO_ACTIVE_LOW>;
> > +		powerdown-gpios = <&port3 12 GPIO_ACTIVE_HIGH>;
> > +
> > +		clocks = <&xclk>;
> > +		clock-names = "xclk";
> > +
> > +		xclk: fixed_clk {
> > +			compatible = "fixed-clock";
> > +			#clock-cells = <0>;
> > +			clock-frequency = <24000000>;
> > +		};
> 
> What's the purpose of the fixed_clk node here?

The sensor is clocked by a 24MHz oscillator. The clock isn't provided by the 
sensor, so it should be located at the root of the device tree, not as a child 
of the sensor DT node.

> > +
> > +		port {
> > +			ov7670_out: endpoint {
> > +				remote-endpoint = <&ceu_in>;
> > +
> > +				bus-width = <8>;
> > +				hsync-active = <1>;
> > +				vsync-active = <1>;
> > +				pclk-sample = <1>;
> > +				data-active = <1>;
> > +			};
> > +		};
> > +	};

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 02/10] include: media: Add Renesas CEU driver interface
  2017-11-15 12:36   ` Sakari Ailus
@ 2017-12-11 14:26     ` Laurent Pinchart
  2017-12-11 14:32       ` Laurent Pinchart
  0 siblings, 1 reply; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-11 14:26 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Jacopo Mondi, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Sakari,

On Wednesday, 15 November 2017 14:36:33 EET Sakari Ailus wrote:
> On Wed, Nov 15, 2017 at 11:55:55AM +0100, Jacopo Mondi wrote:
> > Add renesas-ceu header file.
> > 
> > Do not remove the existing sh_mobile_ceu.h one as long as the original
> > driver does not go away.
> 
> Hmm. This isn't really not about not removing a file but adding a new one.
> Do you really need it outside the driver's own directory?

The file defines platform data structures that are needed for arch/sh/.

> > Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> > ---
> > 
> >  include/media/drv-intf/renesas-ceu.h | 23 +++++++++++++++++++++++
> >  1 file changed, 23 insertions(+)
> >  create mode 100644 include/media/drv-intf/renesas-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..f2da78c
> > --- /dev/null
> > +++ b/include/media/drv-intf/renesas-ceu.h
> > @@ -0,0 +1,23 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +#ifndef __ASM_RENESAS_CEU_H__
> > +#define __ASM_RENESAS_CEU_H__
> > +
> > +#include <media/v4l2-mediabus.h>

I don't think you need this.

> > +#define CEU_FLAG_PRIMARY_SENS	BIT(0)
> > +#define CEU_MAX_SENS		2

I assume SENS stands for sensor. As other sources than sensors can be 
supported (video decoders for instance), I would name this CEU_MAX_SUBDEVS, 
CEU_MAX_INPUTS or something similar.

> > +
> > +struct ceu_async_subdev {
> > +	unsigned long flags;
> > +	unsigned char bus_width;
> > +	unsigned char bus_shift;
> > +	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__ */
> > --
> > 2.7.4


-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 02/10] include: media: Add Renesas CEU driver interface
  2017-12-11 14:26     ` Laurent Pinchart
@ 2017-12-11 14:32       ` Laurent Pinchart
  0 siblings, 0 replies; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-11 14:32 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: Jacopo Mondi, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Jacopo,

On Monday, 11 December 2017 16:26:27 EET Laurent Pinchart wrote:
> On Wednesday, 15 November 2017 14:36:33 EET Sakari Ailus wrote:
> > On Wed, Nov 15, 2017 at 11:55:55AM +0100, Jacopo Mondi wrote:
> >> Add renesas-ceu header file.
> >> 
> >> Do not remove the existing sh_mobile_ceu.h one as long as the original
> >> driver does not go away.
> > 
> > Hmm. This isn't really not about not removing a file but adding a new one.
> > Do you really need it outside the driver's own directory?
> 
> The file defines platform data structures that are needed for arch/sh/.
> 
> >> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> >> ---
> >> 
> >>  include/media/drv-intf/renesas-ceu.h | 23 +++++++++++++++++++++++
> >>  1 file changed, 23 insertions(+)
> >>  create mode 100644 include/media/drv-intf/renesas-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..f2da78c
> >> --- /dev/null
> >> +++ b/include/media/drv-intf/renesas-ceu.h
> >> @@ -0,0 +1,23 @@
> >> +// SPDX-License-Identifier: GPL-2.0+
> >> +#ifndef __ASM_RENESAS_CEU_H__
> >> +#define __ASM_RENESAS_CEU_H__
> >> +
> >> +#include <media/v4l2-mediabus.h>
> 
> I don't think you need this.
> 
> > > +#define CEU_FLAG_PRIMARY_SENS	BIT(0)

I forgot to mention that this flag should be renamed as well, but couldn't we 
get rid of it completely by mandating that the first subdev in the subdevs 
array should be the primary one ?

> > > +#define CEU_MAX_SENS		2
> 
> I assume SENS stands for sensor. As other sources than sensors can be
> supported (video decoders for instance), I would name this CEU_MAX_SUBDEVS,
> CEU_MAX_INPUTS or something similar.
> 
> >> +
> >> +struct ceu_async_subdev {
> >> +	unsigned long flags;
> >> +	unsigned char bus_width;
> >> +	unsigned char bus_shift;
> >> +	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__ */

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 05/10] arch: sh: migor: Use new renesas-ceu camera driver
  2017-11-15 10:55 ` [PATCH v1 05/10] arch: sh: migor: Use new renesas-ceu camera driver Jacopo Mondi
@ 2017-12-11 14:36   ` Laurent Pinchart
  2017-12-12 10:00   ` Laurent Pinchart
  1 sibling, 0 replies; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-11 14:36 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: magnus.damm, geert, mchehab, hverkuil, linux-renesas-soc,
	linux-media, linux-sh, linux-kernel

Hi Jacopo,

Thank you for the patch.

On Wednesday, 15 November 2017 12:55:58 EET Jacopo Mondi wrote:
> Migo-R platform uses sh_mobile_ceu camera driver, which is now being
> replaced by a proper V4L2 camera driver named 'renesas-ceu'.
> 
> Move Migo-R platform to use the v4l2 renesas-ceu camera driver
> interface and get rid of soc_camera defined components used to register
> sensor drivers.
> 
> Also, memory for CEU video buffers is now reserved with membocks APIs,
> and need to be declared as dma_coherent during machine initialization to
> remove that architecture specific part from CEU driver.
> 
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  arch/sh/boards/mach-migor/setup.c | 164 ++++++++++++++++------------------
>  1 file changed, 76 insertions(+), 88 deletions(-)
> 
> diff --git a/arch/sh/boards/mach-migor/setup.c
> b/arch/sh/boards/mach-migor/setup.c index 98aa094..10a9b3c 100644
> --- a/arch/sh/boards/mach-migor/setup.c
> +++ b/arch/sh/boards/mach-migor/setup.c
> @@ -27,7 +27,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>
> @@ -308,62 +308,80 @@ 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.
>  	 */
>  	camera_clk = clk_get(NULL, "video_clk");
>  	clk_set_rate(camera_clk, 10000000);
>  	clk_enable(camera_clk);	/* start VIO_CKO */
> -
> -	/* 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)
> +static void camera_disable(void)
>  {
> -	clk_disable(camera_clk); /* stop VIO_CKO */
> +	/* stop VIO_CKO */
> +	clk_disable(camera_clk);
>  	clk_put(camera_clk);
> 
> +	gpio_set_value(GPIO_PTT0, 0);
> +	gpio_set_value(GPIO_PTT2, 1);
>  	gpio_set_value(GPIO_PTT3, 0);
> +
>  	mutex_unlock(&camera_lock);
>  }
> 
> -static int ov7725_power(struct device *dev, int mode)
> +static void camera_reset(void)
>  {
> -	if (mode)
> -		camera_power_on(0);
> -	else
> -		camera_power_off();
> +	/* use VIO_RST to take camera out of reset */
> +	gpio_set_value(GPIO_PTT3, 0);
> +	mdelay(10);
> +	gpio_set_value(GPIO_PTT3, 1);
> +	mdelay(10);
> +}
> +
> +static int ov7725_enable(void)
> +{
> +	mutex_lock(&camera_lock);
> +	camera_vio_clk_on();
> +	mdelay(10);
> +	gpio_set_value(GPIO_PTT0, 1);
> +
> +	camera_reset();
> 
>  	return 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();
> +	mutex_lock(&camera_lock);
> +	camera_vio_clk_on();
> +	mdelay(10);
> +	gpio_set_value(GPIO_PTT2, 0);
> +
> +	camera_reset();
> 
>  	return 0;
>  }

Can't all these be moved to drivers by using the GPIO, clock and regulator 
APIs ? We should really try to get rid of platform callbacks. Apart from the 
patch patch looks good to me.

> -static struct sh_mobile_ceu_info sh_mobile_ceu_info = {
> -	.flags = SH_CEU_FLAG_USE_8BIT_BUS,
> +static struct ceu_info ceu_info = {
> +	.num_subdevs		= 2,
> +	.subdevs = {
> +		{ /* [0] = ov772x */
> +			.flags		= CEU_FLAG_PRIMARY_SENS,
> +			.bus_width	= 8,
> +			.bus_shift	= 0,
> +			.i2c_adapter_id	= 0,
> +			.i2c_address	= 0x21,
> +		},
> +		{ /* [1] = tw9910 */
> +			.flags		= 0,
> +			.bus_width	= 8,
> +			.bus_shift	= 0,
> +			.i2c_adapter_id	= 0,
> +			.i2c_address	= 0x45,
> +		},
> +	},
>  };
> 
>  static struct resource migor_ceu_resources[] = {
> @@ -377,18 +395,15 @@ static struct resource migor_ceu_resources[] = {
>  		.start  = evt2irq(0x880),
>  		.flags  = IORESOURCE_IRQ,
>  	},
> -	[2] = {
> -		/* place holder for contiguous memory */
> -	},
>  };
> 
>  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,
>  	},
>  };
> 
> @@ -427,6 +442,19 @@ static struct platform_device sdhi_cn9_device = {
>  	},
>  };
> 
> +static struct ov772x_camera_info ov7725_info = {
> +	.platform_enable = ov7725_enable,
> +	.platform_disable = camera_disable,
> +};
> +
> +static struct tw9910_video_info tw9910_info = {
> +	.buswidth       = TW9910_DATAWIDTH_8,
> +	.mpout          = TW9910_MPO_FIELD,
> +
> +	.platform_enable = tw9910_enable,
> +	.platform_disable = camera_disable,
> +};
> +
>  static struct i2c_board_info migor_i2c_devices[] = {
>  	{
>  		I2C_BOARD_INFO("rs5c372b", 0x32),
> @@ -438,51 +466,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,12 +480,9 @@ static struct platform_device *migor_devices[]
> __initdata = { &smc91x_eth_device,
>  	&sh_keysc_device,
>  	&migor_lcdc_device,
> -	&migor_ceu_device,
>  	&migor_nor_flash_device,
>  	&migor_nand_flash_device,
>  	&sdhi_cn9_device,
> -	&migor_camera[0],
> -	&migor_camera[1],
>  };
> 
>  extern char migor_sdram_enter_start;
> @@ -505,8 +492,6 @@ extern char migor_sdram_leave_end;
> 
>  static int __init migor_devices_setup(void)
>  {
> -	struct resource *r;
> -
>  	/* register board specific self-refresh code */
>  	sh_mobile_register_self_refresh(SUSP_SH_STANDBY | SUSP_SH_SF,
>  					&migor_sdram_enter_start,
> @@ -651,16 +636,19 @@ static int __init migor_devices_setup(void)
>  	 */
>  	__raw_writew(__raw_readw(PORT_MSELCRA) | 1, PORT_MSELCRA);
> 
> -	/* Setup additional memory resource for CEU video buffers */
> -	r = &migor_ceu_device.resource[2];
> -	r->flags = IORESOURCE_MEM;
> -	r->start = ceu_dma_membase;
> -	r->end = r->start + CEU_BUFFER_MEMORY_SIZE - 1;
> -	r->name = "ceu";
> -
>  	i2c_register_board_info(0, migor_i2c_devices,
>  				ARRAY_SIZE(migor_i2c_devices));
> 
> +	/* Initialize CEU platform device separately to map memory first */
> +	device_initialize(&migor_ceu_device.dev);
> +	arch_setup_pdev_archdata(&migor_ceu_device);
> +	dma_declare_coherent_memory(&migor_ceu_device.dev,
> +				    ceu_dma_membase, ceu_dma_membase,
> +				    ceu_dma_membase + CEU_BUFFER_MEMORY_SIZE - 1,
> +				    DMA_MEMORY_EXCLUSIVE);
> +
> +	platform_device_add(&migor_ceu_device);
> +
>  	return platform_add_devices(migor_devices, ARRAY_SIZE(migor_devices));
>  }
>  arch_initcall(migor_devices_setup);

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 08/10] media: i2c: ov772x: Remove soc_camera dependencies
  2017-11-15 10:56 ` [PATCH v1 08/10] media: i2c: ov772x: Remove soc_camera dependencies Jacopo Mondi
  2017-11-17  0:43   ` Sakari Ailus
@ 2017-12-11 14:47   ` Laurent Pinchart
  1 sibling, 0 replies; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-11 14:47 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: magnus.damm, geert, mchehab, hverkuil, linux-renesas-soc,
	linux-media, linux-sh, linux-kernel

Hi Jacopo,

Thank you for the patch.

On Wednesday, 15 November 2017 12:56:01 EET Jacopo Mondi wrote:
> Remove soc_camera framework dependencies from ov772x sensor driver.
> - Handle clock directly
> - Register async subdevice
> - Add platform specific enable/disable functions
> - Adjust build system
> 
> This commit does not remove the original soc_camera based driver.
> 
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  drivers/media/i2c/Kconfig  | 12 +++++++
>  drivers/media/i2c/Makefile |  1 +
>  drivers/media/i2c/ov772x.c | 88 ++++++++++++++++++++++++++++---------------
>  include/media/i2c/ov772x.h |  3 ++
>  4 files changed, 76 insertions(+), 28 deletions(-)
> 
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index 9415389..ff251ce 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -629,6 +629,18 @@ config VIDEO_OV5670
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called ov5670.
> 
> +config VIDEO_OV772X
> +	tristate "OmniVision OV772x sensor support"
> +	depends on I2C && VIDEO_V4L2
> +	depends on MEDIA_CAMERA_SUPPORT
> +	---help---
> +	  This is a Video4Linux2 sensor-level driver for the OmniVision
> +	  OV772x camera.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called ov772x.
> +
> +

A single blank line is enough.

>  config VIDEO_OV7640
>  	tristate "OmniVision OV7640 sensor support"
>  	depends on I2C && VIDEO_V4L2
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index f104650..b2459a1 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -66,6 +66,7 @@ obj-$(CONFIG_VIDEO_OV5645) += ov5645.o
>  obj-$(CONFIG_VIDEO_OV5647) += ov5647.o
>  obj-$(CONFIG_VIDEO_OV5670) += ov5670.o
>  obj-$(CONFIG_VIDEO_OV6650) += ov6650.o
> +obj-$(CONFIG_VIDEO_OV772X) += ov772x.o
>  obj-$(CONFIG_VIDEO_OV7640) += ov7640.o
>  obj-$(CONFIG_VIDEO_OV7670) += ov7670.o
>  obj-$(CONFIG_VIDEO_OV9650) += ov9650.o
> diff --git a/drivers/media/i2c/ov772x.c b/drivers/media/i2c/ov772x.c
> index 8063835..9be7e4e 100644
> --- a/drivers/media/i2c/ov772x.c
> +++ b/drivers/media/i2c/ov772x.c

[snip]

> @@ -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;

Then please don't call clk_enable() if priv->clk is NULL, and propagate errors 
from clk_enable() back to the caller.

And shouldn't you call clk_prepare_enable() (and clk_disable_unprepare() 
below) ?

> +}
> +
> +static int ov772x_power_off(struct ov772x_priv *priv)
> +{
> +	if (priv->info->platform_enable)
> +		priv->info->platform_disable();
> +
> +	clk_disable(priv->clk);
> +
> +	return 0;
> +}

[snip]

> @@ -1073,21 +1092,33 @@ 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;

You need a priv->clk = NULL here otherwise the error path will oops.

> +		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);

clk_put() accepts a NULL clock, you don't have to check the pointer first.

> +error_clk_enable:
> +	v4l2_ctrl_handler_free(&priv->hdl);
> 
>  	return ret;
>  }
> @@ -1096,7 +1127,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);

Same here.

>  	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__ */

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 07/10] v4l: i2c: Copy ov772x soc_camera sensor driver
  2017-11-15 10:56 ` [PATCH v1 07/10] v4l: i2c: Copy ov772x soc_camera sensor driver Jacopo Mondi
@ 2017-12-11 14:49   ` Laurent Pinchart
  2017-12-13 12:10   ` Hans Verkuil
  2017-12-13 13:02   ` Philippe Ombredanne
  2 siblings, 0 replies; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-11 14:49 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: magnus.damm, geert, mchehab, hverkuil, linux-renesas-soc,
	linux-media, linux-sh, linux-kernel

Hi Jacopo,

Thank you for the patch.

On Wednesday, 15 November 2017 12:56:00 EET Jacopo Mondi wrote:
> Copy the soc_camera based driver in v4l2 sensor driver directory.
> This commit just copies the original file without modifying it.

You might want to explain why you're not patching the Kconfig and Makefile 
here, that is because you will first convert the driver away from soc-camera 
in the next commit.

Apart from that,

Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  drivers/media/i2c/ov772x.c | 1124 +++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 1124 insertions(+)
>  create mode 100644 drivers/media/i2c/ov772x.c

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 09/10] v4l: i2c: Copy tw9910 soc_camera sensor driver
  2017-11-15 10:56 ` [PATCH v1 09/10] v4l: i2c: Copy tw9910 soc_camera sensor driver Jacopo Mondi
@ 2017-12-11 14:50   ` Laurent Pinchart
  0 siblings, 0 replies; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-11 14:50 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: magnus.damm, geert, mchehab, hverkuil, linux-renesas-soc,
	linux-media, linux-sh, linux-kernel

Hi Jacopo,

Thank you for the patch.

On Wednesday, 15 November 2017 12:56:02 EET Jacopo Mondi wrote:
> Copy the soc_camera based driver in v4l2 sensor driver directory.
> This commit just copies the original file without modifying it.

As for patch 07/10, you might want to explain why you're not patching the 
Kconfig and Makefile here, that is because you will first convert the driver 
away from soc-camera in the next commit.

Apart from that,

Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  drivers/media/i2c/tw9910.c | 999 ++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 999 insertions(+)
>  create mode 100644 drivers/media/i2c/tw9910.c

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 10/10] media: i2c: tw9910: Remove soc_camera dependencies
  2017-11-15 10:56 ` [PATCH v1 10/10] media: i2c: tw9910: Remove soc_camera dependencies Jacopo Mondi
@ 2017-12-11 14:55   ` Laurent Pinchart
  2017-12-13 12:13   ` Hans Verkuil
  1 sibling, 0 replies; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-11 14:55 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: magnus.damm, geert, mchehab, hverkuil, linux-renesas-soc,
	linux-media, linux-sh, linux-kernel

Hi Jacopo,

Thank you for the patch.

On Wednesday, 15 November 2017 12:56:03 EET Jacopo Mondi wrote:
> Remove soc_camera framework dependencies from tw9910 sensor driver.
> - Handle clock directly
> - Register async subdevice
> - Add platform specific enable/disable functions
> - Adjust build system
> 
> This commit does not remove the original soc_camera based driver.
> 
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  drivers/media/i2c/Kconfig  |  9 ++++++
>  drivers/media/i2c/Makefile |  1 +
>  drivers/media/i2c/tw9910.c | 80 +++++++++++++++++++++++++++++++------------
>  include/media/i2c/tw9910.h |  6 ++++
>  4 files changed, 75 insertions(+), 21 deletions(-)

[snip]

> diff --git a/drivers/media/i2c/tw9910.c b/drivers/media/i2c/tw9910.c
> index bdb5e0a..f422da2 100644
> --- a/drivers/media/i2c/tw9910.c
> +++ b/drivers/media/i2c/tw9910.c

[snip]

> @@ -582,13 +581,40 @@ 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;
> +	}
> +
> +	if (priv->clk)
> +		return clk_enable(priv->clk);

Shouldn't you use clk_prepare_enable() here ?

> +	return 0;
> +}
> +
> +static int tw9910_power_off(struct tw9910_priv *priv)
> +{
> +	if (priv->info->platform_enable)
> +		priv->info->platform_disable();
> +
> +	if (priv->clk)
> +		clk_disable(priv->clk);

And clk_disable_unprepare() here ?

> +
> +	return 0;
> +}

[snip]

> @@ -959,13 +979,27 @@ 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);
> +		goto error_put_clk;
> +
> +	ret = v4l2_async_register_subdev(&priv->subdev);
> +	if (ret)
> +		goto error_put_clk;
> +
> +	return ret;
> +
> +error_put_clk:
> +	if (priv->clk)
> +		clk_put(priv->clk);

No need to check if priv->clk is NULL here, clk_put() should handle that 
properly.

>  	return ret;
>  }
> @@ -973,7 +1007,11 @@ 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);

Same here.

> +	v4l2_device_unregister_subdev(&priv->subdev);
> +
>  	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;

How about storing that as an unsigned int that takes values 8 or 16 ? It would 
be more explicit (and a bit of kerneldoc for the tw9910_video_info structure 
would make that even better).

> +	int (*platform_enable)(void);
> +	void (*platform_disable)(void);
>  };

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-11-17  0:36       ` Sakari Ailus
  2017-11-17  9:33         ` jacopo mondi
@ 2017-12-11 15:04         ` Laurent Pinchart
  1 sibling, 0 replies; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-11 15:04 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: jacopo mondi, Jacopo Mondi, magnus.damm, geert, mchehab,
	hverkuil, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Sakari,

On Friday, 17 November 2017 02:36:51 EET Sakari Ailus wrote:
> On Wed, Nov 15, 2017 at 03:25:11PM +0100, jacopo mondi wrote:
> > On Wed, Nov 15, 2017 at 02:45:51PM +0200, Sakari Ailus wrote:
> >> Hi Jacopo,
> >> 
> >> Could you remove the original driver and send the patch using git
> >> send-email -C ? That way a single patch would address converting it to a
> >> proper V4L2 driver as well as move it to the correct location. The
> >> changes would be easier to review that way since then, well, it'd be
> >> easier to see the changes. :-)
> > 
> > Actually I prefer not to remove the existing driver at the moment. See
> > the cover letter for reasons why not to do so right now...
> 
> So it's about testing mostly? Does someone (possibly you) have those boards
> to test? I'd like to see this patchset to remove that last remaining SoC
> camera bridge driver. :-)

Unfortunately there's also drivers/media/platform/pxa-camera.c :-(

> > Also, there's not that much code from the old driver in here, surely
> > less than the default 50% -C and -M options of 'git format-patch' use
> > as a threshold for detecting copies iirc..
> 
> Oh, if that's so, then makes sense to review it as a new driver.

Yes, unfortunately the drivers are too different. Jacopo started developing an 
incremental patch series to move the driver away from soc-camera, but in the 
end we decided to stop following that path as it was too painful. It's easier 
to review a new driver in this case.

> > I would prefer this to be reviewed as new driver, I know it's a bit
> > more painful, but irq handler and a couple of other routines apart,
> > there's not that much code shared between the two...
> > 
> >> The same goes for the two V4L2 SoC camera sensor / video decoder drivers
> >> at the end of the set.
> > 
> > Also in this case I prefer not to remove existing code, as long as
> > there are platforms using it..
> 
> Couldn't they use this driver instead?
> 
> >> On Wed, Nov 15, 2017 at 11:55:56AM +0100, Jacopo Mondi wrote:
> >>> Add driver for Renesas Capture Engine Unit (CEU).
> >>> 
> >>> The CEU interface supports capturing 'data' (YUV422) and 'images'
> >>> (NV[12|21|16|61]).
> >>> 
> >>> This driver aims to replace the soc_camera based sh_mobile_ceu one.
> >>> 
> >>> Tested with ov7670 camera sensor, providing YUYV_2X8 data on Renesas
> >>> RZ platform GR-Peach.
> >>> 
> >>> Tested with ov7725 camera sensor on SH4 platform Migo-R.
> >> 
> >> Nice!
> >> 
> >>> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> >>> ---
> >>> +#include <linux/completion.h>
> >> 
> >> Do you need this header? There would seem some that I wouldn't expect to
> >> be needed below, such as linux/init.h.
> > 
> > It's probably a leftover, I'll remove it...
> > 
> > [snip]
> > 
> >>> +#if IS_ENABLED(CONFIG_OF)
> >>> +static const struct of_device_id ceu_of_match[] = {
> >>> +	{ .compatible = "renesas,renesas-ceu" },
> >> 
> >> Even if you add support for new hardware, shouldn't you maintain support
> >> for renesas,sh-mobile-ceu?
> > 
> > As you noticed already, the old driver did not support OF, so there
> > are no compatibility issues here
> 
> Yeah, I realised that only after reviewing this patch.
> 
> It'd be Super-cool if someone did the DT conversion. Perhaps Laurent? ;-)

Or you ? :-)

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-11-15 10:55 ` [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver Jacopo Mondi
  2017-11-15 12:45   ` Sakari Ailus
@ 2017-12-11 16:15   ` Laurent Pinchart
  2017-12-18 12:25     ` jacopo mondi
  2017-12-19 11:57     ` jacopo mondi
  2017-12-13 12:03   ` Hans Verkuil
  2 siblings, 2 replies; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-11 16:15 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: magnus.damm, geert, mchehab, hverkuil, linux-renesas-soc,
	linux-media, linux-sh, linux-kernel

Hi Jacopo,

Thank you for the patch.

On Wednesday, 15 November 2017 12:55:56 EET Jacopo Mondi wrote:
> Add driver for Renesas Capture Engine Unit (CEU).
> 
> The CEU interface supports capturing 'data' (YUV422) and 'images'
> (NV[12|21|16|61]).
> 
> This driver aims to replace the soc_camera based sh_mobile_ceu one.

s/soc_camera based/soc_camera-based/

> Tested with ov7670 camera sensor, providing YUYV_2X8 data on Renesas RZ
> platform GR-Peach.
> 
> Tested with ov7725 camera sensor on SH4 platform Migo-R.
> 
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  drivers/media/platform/Kconfig       |    9 +
>  drivers/media/platform/Makefile      |    2 +
>  drivers/media/platform/renesas-ceu.c | 1768 +++++++++++++++++++++++++++++++
>  3 files changed, 1779 insertions(+)
>  create mode 100644 drivers/media/platform/renesas-ceu.c

[snip]

> diff --git a/drivers/media/platform/renesas-ceu.c
> b/drivers/media/platform/renesas-ceu.c new file mode 100644
> index 0000000..aaba3cd
> --- /dev/null
> +++ b/drivers/media/platform/renesas-ceu.c
> @@ -0,0 +1,1768 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * V4L2 Driver for Renesas Capture Engine Unit (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.

You can use an SPDX header instead of a copyright text. Start the file with

// SPDX-License-Identifier: GPL-2.0+

and remove this paragraph.

> + */

[snip]

> +/* mbus configuration flags */
> +#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
> + * @bpp: number of bits per pixels unit
> + */
> +struct ceu_mbus_fmt {
> +	u32	mbus_code;
> +	u32	fmt_order;
> +	u32	fmt_order_swap;
> +	bool	swapped;
> +	u8	bps;
> +	u8	bpp;
> +};
> +
> +/**
> + * ceu_buffer - Link vb2 buffer to the list of available buffers

If you use kerneldoc comments please make them compile. You need to document 
the structure fields and function arguments.

> + */
> +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_subdev - Wraps v4l2 sub-device and provides async subdevice.
> + */
> +struct ceu_subdev {
> +	struct v4l2_subdev *v4l2_sd;
> +	struct v4l2_async_subdev asd;
> +
> +	/* per-subdevice mbus configuration options */
> +	unsigned int mbus_flags;
> +	struct ceu_mbus_fmt mbus_fmt;
> +};
> +
> +static struct ceu_subdev *to_ceu_subdev(struct v4l2_async_subdev *asd)
> +{
> +	return container_of(asd, struct ceu_subdev, asd);
> +}
> +
> +/**
> + * ceu_device - CEU device instance
> + */
> +struct ceu_device {
> +	struct device		*dev;
> +	struct video_device	vdev;
> +	struct v4l2_device	v4l2_dev;
> +
> +	/* subdevices descriptors */
> +	struct ceu_subdev	*subdevs;
> +	/* the subdevice currently in use */
> +	struct ceu_subdev	*sd;
> +	unsigned int		sd_index;
> +	unsigned int		num_sd;
> +
> +	/* currently configured field and pixel format */
> +	enum v4l2_field	field;
> +	struct v4l2_pix_format_mplane v4l2_pix;
> +
> +	/* async subdev notification helpers */
> +	struct v4l2_async_notifier notifier;
> +	/* pointers to "struct ceu_subdevice -> asd" */
> +	struct v4l2_async_subdev **asds;
> +
> +	/* 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 */

Lock documentation should usually explain what data is protected by the lock.

> +	struct mutex	mlock;
> +
> +	/* lock - lock access to capture buffer queue and active buffer */
> +	spinlock_t	lock;
> +
> +	/* base - CEU memory base address */
> +	void __iomem	*base;
> +};
> +
> +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

Do you mean number of bits ?

> + */
> +struct ceu_fmt {
> +	u32	fourcc;
> +	u8	bpp;

This will take 32 bits anyway due to alignment constraints, I'd make the field 
an unsigned int.

> +};
> +
> +/**
> + * 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
> + * capabilities.
> + */
> +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,
> +	},
> +	{
> +		.fourcc	= V4L2_PIX_FMT_YUYV,
> +		.bpp	= 16,
> +	},
> +};
> +
> +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;
> +}
> +
> +static inline bool ceu_is_fmt_planar(struct v4l2_pix_format_mplane *pix)

No need for the inline keyword, the compiler should be smart enough to decide.

> +{
> +	switch (pix->pixelformat) {
> +	case V4L2_PIX_FMT_YUYV:
> +		return false;
> +	case V4L2_PIX_FMT_NV16:
> +	case V4L2_PIX_FMT_NV61:
> +	case V4L2_PIX_FMT_NV12:
> +	case V4L2_PIX_FMT_NV21:
> +		return true;
> +	}
> +
> +	return true;

Maybe a default case instead ?

> +}

[snip]

> +/**
> + * ceu_soft_reset() - Software reset the CEU interface
> + */
> +static int ceu_soft_reset(struct ceu_device *ceudev)
> +{
> +	unsigned int reset_done;
> +	unsigned int i;
> +
> +	ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CPKIL);
> +
> +	reset_done = 0;
> +	for (i = 0; i < 1000 && !reset_done; i++) {
> +		udelay(1);
> +		if (!(ceu_read(ceudev, CEU_CSTSR) & CEU_CSTRST_CPTON))
> +			reset_done++;
> +	}

How many iterations does this typically require ? Wouldn't a sleep be better 
than a delay ? As far as I can tell the ceu_soft_reset() function is only 
called with interrupts disabled (in interrupt context) from ceu_capture() in 
an error path, and that code should be reworked to make it possible to sleep 
if a reset takes too long.

> +	if (!reset_done) {
> +		v4l2_err(&ceudev->v4l2_dev, "soft reset time out\n");

How about dev_err() instead ?

> +		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 capture.
> + */
> +static int ceu_capture(struct ceu_device *ceudev)
> +{
> +	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> +	dma_addr_t phys_addr_top;
> +	u32 status;
> +
> +	/* Clean interrupt status and re-enable interrupts */
> +	status = ceu_read(ceudev, CEU_CETCR);
> +	ceu_write(ceudev, CEU_CEIER,
> +		  ceu_read(ceudev, CEU_CEIER) & ~CEU_CEIER_MASK);
> +	ceu_write(ceudev, CEU_CETCR, ~status & CEU_CETCR_MAGIC);
> +	ceu_write(ceudev, CEU_CEIER, CEU_CEIER_MASK);

I wonder why there's a need to disable and reenable interrupts here.

> +	ceu_write(ceudev, CEU_CAPCR,
> +		  ceu_read(ceudev, CEU_CAPCR) & ~CEU_CAPCR_CTNCP);
> +
> +	/*
> +	 * 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) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "VBP interrupt while capturing\n");
> +		ceu_soft_reset(ceudev);
> +		return -EIO;
> +	} else if (!ceudev->active) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "No available buffers for capture\n");
> +		return -EINVAL;
> +	}
> +
> +	phys_addr_top =
> +		vb2_dma_contig_plane_dma_addr(&ceudev->active->vb2_buf, 0);
> +	ceu_write(ceudev, CEU_CDAYR, phys_addr_top);
> +
> +	/* Ignore CbCr plane in data sync mode */
> +	if (ceu_is_fmt_planar(pix)) {
> +		phys_addr_top =
> +			vb2_dma_contig_plane_dma_addr(&ceudev->active->vb2_buf,
> +						      1);
> +		ceu_write(ceudev, CEU_CDACR, phys_addr_top);
> +	}
> +
> +	/*
> +	 * 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 0;
> +}
> +
> +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;

Shouldn't you at least clear the interrupt source (done at the beginning of 
the ceu_capture() function) in this case ? I'd move the handling of the 
interrupt status from ceu_capture() to here and pass the status to the capture 
function. Or even handle the status here completely, as status handling isn't 
needed when ceu_capture() is called from ceu_start_streaming().

> +	/* Prepare a new 'active' buffer and trigger a new capture */
> +	buf = vb2_to_ceu(vbuf);
> +	vbuf->vb2_buf.timestamp = ktime_get_ns();
> +
> +	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;
> +	}
> +
> +	/*
> +	 * If the new capture started successfully, mark the previous buffer
> +	 * as "DONE".
> +	 */
> +	ret = ceu_capture(ceudev);
> +	if (!ret) {
> +		vbuf->field = ceudev->field;
> +		vbuf->sequence = ceudev->sequence++;

Shouldn't you set the sequence number even when an error occurs ? You should 
also complete all buffers with VB2_BUF_STATE_ERROR in that case, as 
ceu_capture() won't start a new capture, otherwise userspace will hang 
forever.

> +	}
> +
> +	vb2_buffer_done(&vbuf->vb2_buf,
> +			ret < 0 ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
> +
> +out:
> +	spin_unlock(&ceudev->lock);
> +
> +	return IRQ_HANDLED;

You shouldn't return IRQ_HANDLED if the IRQ status reported no interrupt.

> +}
> +
> +/* ------------------------------------------------------------------------
> + * CEU Videobuf operations
> + */
> +
> +/**
> + * ceu_calc_plane_sizes() - Fill 'struct v4l2_plane_pix_format' per plane
> + *			    information according to the currently configured
> + *			    pixel format.
> + */
> +static int ceu_calc_plane_sizes(struct ceu_device *ceudev,
> +				const struct ceu_fmt *ceu_fmt,
> +				struct v4l2_pix_format_mplane *pix)
> +{
> +	struct v4l2_plane_pix_format *plane_fmt = &pix->plane_fmt[0];
> +
> +	switch (pix->pixelformat) {
> +	case V4L2_PIX_FMT_YUYV:
> +		pix->num_planes			= 1;
> +		plane_fmt[0].bytesperline	= pix->width * ceu_fmt->bpp / 8;

Doesn't the driver support configurable stride ?

> +		plane_fmt[0].sizeimage		= pix->height *
> +						  plane_fmt[0].bytesperline;

Padding at the end of the image should be allowed if requested by userspace.

> +		break;
> +	case V4L2_PIX_FMT_NV16:
> +	case V4L2_PIX_FMT_NV61:
> +		pix->num_planes			= 2;
> +		plane_fmt[0].bytesperline	= pix->width;
> +		plane_fmt[0].sizeimage		= pix->height * pix->width;
> +		plane_fmt[1]			= plane_fmt[0];
> +		break;
> +	case V4L2_PIX_FMT_NV12:
> +	case V4L2_PIX_FMT_NV21:
> +		pix->num_planes			= 2;
> +		plane_fmt[0].bytesperline	= pix->width;
> +		plane_fmt[0].sizeimage		= pix->height * pix->width;
> +		plane_fmt[1].bytesperline	= pix->width;
> +		plane_fmt[1].sizeimage		= pix->height * pix->width / 2;
> +		break;
> +	default:

Can this happen ? ceu_try_fmt() should have validated the format.

> +		pix->num_planes			= 0;
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Format 0x%x not supported\n", pix->pixelformat);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * ceu_videobuf_setup() - is called to check, whether the driver can accept

s/check,/check/

> 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_mplane *pix = &ceudev->v4l2_pix;
> +	unsigned int i;
> +
> +	if (!vq->num_buffers)
> +		ceudev->sequence = 0;

Why do you reset the sequence number here, shouldn't it be done at stream 
start instead ?

> +	if (!*count)
> +		*count = 2;
> +
> +	/* num_planes is set: just check plane sizes */
> +	if (*num_planes) {
> +		for (i = 0; i < pix->num_planes; i++) {
> +			if (sizes[i] < pix->plane_fmt[i].sizeimage)
> +				return -EINVAL;
> +		}
> +
> +		return 0;
> +	}
> +
> +	/* num_planes not set: called from REQBUFS, just set plane sizes */
> +	*num_planes = pix->num_planes;
> +	for (i = 0; i < pix->num_planes; i++)
> +		sizes[i] = pix->plane_fmt[i].sizeimage;
> +
> +	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 v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> +	struct ceu_buffer *buf = vb2_to_ceu(vbuf);
> +	unsigned long irqflags;
> +	unsigned int i;
> +
> +	for (i = 0; i < pix->num_planes; i++) {
> +		if (vb2_plane_size(vb, i) < pix->plane_fmt[i].sizeimage) {
> +			v4l2_err(&ceudev->v4l2_dev,
> +				 "Buffer #%d too small (%lu < %u)\n",
> +				 vb->index, vb2_plane_size(vb, i),
> +				 pix->plane_fmt[i].sizeimage);

I wouldn't print an error message, otherwise userspace will have yet another 
way to flood the kernel log.

> +			goto error;
> +		}
> +
> +		vb2_set_plane_payload(vb, i, pix->plane_fmt[i].sizeimage);
> +	}
> +
> +	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);

You can inline this call, get rid of the error label and the return statement.

> +}
> +
> +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->sd->v4l2_sd;
> +	struct ceu_buffer *buf;
> +	unsigned long irqflags;
> +	int ret = 0;

No need to assign ret to 0.

> +	ret = v4l2_subdev_call(v4l2_sd, video, s_stream, 1);
> +	if (ret && ret != -ENOIOCTLCMD) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Subdevice failed to start streaming: %d\n", ret);
> +		goto error_return_bufs;
> +	}
> +
> +	spin_lock_irqsave(&ceudev->lock, irqflags);
> +	ceudev->sequence = 0;
> +
> +	if (ceudev->active) {

Can this happen ?

> +		ret = -EINVAL;
> +		spin_unlock_irqrestore(&ceudev->lock, irqflags);
> +		goto error_stop_sensor;
> +	}
> +
> +	/* Grab the first available buffer and trigger the first capture. */
> +	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);

You can move this call out of the error check just after ceu_capture().

> +		goto error_stop_sensor;
> +	}
> +
> +	spin_unlock_irqrestore(&ceudev->lock, irqflags);
> +
> +	return 0;
> +
> +error_stop_sensor:
> +	v4l2_subdev_call(v4l2_sd, 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 v4l2_subdev *v4l2_sd = ceudev->sd->v4l2_sd;
> +	struct ceu_buffer *buf;
> +	unsigned long irqflags;
> +
> +	v4l2_subdev_call(v4l2_sd, 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(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> +	INIT_LIST_HEAD(&ceudev->capture);
> +
> +	spin_unlock_irqrestore(&ceudev->lock, irqflags);
> +
> +	ceu_soft_reset(ceudev);
> +}

[snip]

> +/**

Is this really kerneldoc ?

> + * ------------------------------------------------------------------------
> + *  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:

Can this happen ? You should reject non-parallel sensors at probe time (or 
subdev bind time).

> +		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 errors
> + *	    0 if g_mbus_config is not supported,
> + *	    > 0  for bus configuration flags supported by (ceu AND sensor)
> + */
> +static int ceu_test_mbus_param(struct ceu_device *ceudev)
> +{
> +	struct v4l2_subdev *sd = ceudev->sd->v4l2_sd;
> +	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;

This is a legacy of soc_camera that tried to negotiate bus parameters with the 
source subdevice. We have later established that this isn't a good idea, as 
there could be components on the board that affect those settings (for 
instance inverters on the synchronization signals). This is why with DT we 
specify the bus configuration in endpoints on both sides. You should thus 
always use the bus configuration provided through DT or platform data and 
ignore the one reported by the subdev.

> +}
> +
> +/**
> + * ceu_set_bus_params() - Configure CEU interface registers using bus
> + *			  parameters
> + */
> +static int ceu_set_bus_params(struct ceu_device *ceudev)
> +{
> +	u32 camcr = 0, cdocr = 0, cfzsr = 0, cdwdr = 0, capwr = 0;

No need to assign a value to cdocr, cfzsr, cdwdr and capwr.

> +	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> +	struct ceu_subdev *ceu_sd = ceudev->sd;
> +	struct ceu_mbus_fmt *mbus_fmt = &ceu_sd->mbus_fmt;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	unsigned int mbus_flags = ceu_sd->mbus_flags;
> +	unsigned long common_flags = CEU_BUS_FLAGS;

No need to assign a value to common_flags.

> +	int ret;
> +	struct v4l2_mbus_config cfg = {
> +		.type = V4L2_MBUS_PARALLEL,
> +	};
> +
> +	/*
> +	 * If client doesn't implement g_mbus_config, we just use our
> +	 * platform data.
> +	 */
> +	ret = ceu_test_mbus_param(ceudev);
> +	if (ret < 0)
> +		return ret;
> +	else if (ret == 0)
> +		common_flags = ceudev->sd->mbus_flags;
> +	else
> +		common_flags = ret;
> +
> +	/*
> +	 * If the we can choose between multiple alternatives select
> +	 * active high polarities.
> +	 */
> +	if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) &&
> +	    (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) {
> +		if (mbus_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
> +			common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW;
> +		else
> +			common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH;
> +	}
> +
> +	if ((common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) &&
> +	    (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) {
> +		if (mbus_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
> +			common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_LOW;
> +		else
> +			common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_HIGH;
> +	}
> +
> +	cfg.flags = common_flags;
> +	ret = v4l2_subdev_call(v4l2_sd, video, s_mbus_config, &cfg);

For the same reasons described above, don't call .s_mbus_config(), but update 
the sensor drivers to apply the configuration specified in DT or platform 
data.

> +	if (ret < 0 && ret != -ENOIOCTLCMD)
> +		return ret;
> +
> +	/* Start configuring CEU registers */
> +	ceu_write(ceudev, CEU_CAIFR, 0);
> +	ceu_write(ceudev, CEU_CFWCR, 0);
> +	ceu_write(ceudev, CEU_CRCNTR, 0);
> +	ceu_write(ceudev, CEU_CRCMPR, 0);
> +
> +	/* Set the frame capture period for both image capture and data sync */
> +	capwr = (pix->height << 16) | pix->width * mbus_fmt->bpp / 8;
> +
> +	/*
> +	 * Swap input data endianness by default.
> +	 * 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)
> +	 *
> +	 * Use CEU_CDOCR[2:0] to swap data ordering.
> +	 */
> +	cdocr = CEU_CDOCR_SWAP_ENDIANNESS;
> +
> +	/*
> +	 * Configure CAMCR and CDOCR:
> +	 * match input components ordering with memory output format and
> +	 * handle downsampling to YUV420.
> +	 *
> +	 * If the memory output planar format is 'swapped' (Cr before Cb) and
> +	 * input format is not, use the swapped version of CAMCR.DTARY.
> +	 *
> +	 * If the memory output planar 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
> +	 *
> +	 * No downsample for data fetch sync mode.
> +	 */
> +	switch (pix->pixelformat) {
> +	/* data fetch sync mode */
> +	case V4L2_PIX_FMT_YUYV:
> +		/* TODO: handle YUYV permutations through DTARY bits */
> +		camcr	|= CEU_CAMCR_JPEG;
> +		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
> +		cfzsr	= (pix->height << 16) | pix->width;
> +		cdwdr	= pix->plane_fmt[0].bytesperline;
> +		break;
> +
> +	/* non-swapped planar image capture mode */
> +	case V4L2_PIX_FMT_NV16:
> +		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
> +	case V4L2_PIX_FMT_NV12:
> +		if (mbus_fmt->swapped)
> +			camcr |= mbus_fmt->fmt_order_swap;
> +		else
> +			camcr |= mbus_fmt->fmt_order;
> +
> +		cfzsr	= (pix->height << 16) | pix->width;
> +		cdwdr	= pix->width;
> +		break;
> +
> +	/* swapped planar image capture mode */
> +	case V4L2_PIX_FMT_NV61:
> +		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
> +	case V4L2_PIX_FMT_NV21:
> +		if (mbus_fmt->swapped)
> +			camcr |= mbus_fmt->fmt_order;
> +		else
> +			camcr |= mbus_fmt->fmt_order_swap;
> +
> +		cfzsr	= (pix->height << 16) | pix->width;
> +		cdwdr	= pix->width;
> +		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 with DTIF bit in CAMCR */
> +	ceu_write(ceudev, CEU_CAMCR, camcr);
> +	ceu_write(ceudev, CEU_CDOCR, cdocr);
> +	ceu_write(ceudev, CEU_CAPCR, CEU_CAPCR_BUS_WIDTH256);
> +
> +	/*
> +	 * TODO: make CAMOR offsets configurable.
> +	 * CAMOR wants to know the number of blanks between a VS/HS signal
> +	 * and valid data. This value should actually come from the sensor...
> +	 */
> +	ceu_write(ceudev, CEU_CAMOR, 0);
> +
> +	/* TODO: 16 bit bus width require re-calculation of cdwdr and cfzsr */
> +	ceu_write(ceudev, CEU_CAPWR, capwr);
> +	ceu_write(ceudev, CEU_CFSZR, cfzsr);
> +	ceu_write(ceudev, CEU_CDWDR, cdwdr);
> +
> +	return 0;
> +}
> +
> +/**

Is this really kerneldoc ?

> + * ------------------------------------------------------------------------
> + *  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 v4l2_pix_format_mplane *pix = &v4l2_fmt->fmt.pix_mp;
> +	struct ceu_subdev *ceu_sd = ceudev->sd;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	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_YUYV:
> +	case V4L2_PIX_FMT_NV16:
> +	case V4L2_PIX_FMT_NV61:
> +	case V4L2_PIX_FMT_NV12:
> +	case V4L2_PIX_FMT_NV21:
> +		break;
> +
> +	default:
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Pixel format 0x%x not supported, default to NV16\n",
> +			 pix->pixelformat);

I wouldn't print an error message, otherwise userspace will have yet another 
way to flood the kernel log.

> +		pix->pixelformat = V4L2_PIX_FMT_NV16;
> +	}
> +
> +	ceu_fmt = get_ceu_fmt_from_fourcc(pix->pixelformat);
> +
> +	/* 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);

Then why do you align them to 2 ? :-)

> +	/*
> +	 * Set format on sensor sub device: bus format used to produce memory
> +	 * format is selected at initialization time
> +	 */
> +	v4l2_fill_mbus_format_mplane(&sd_format.format, pix);
> +	ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, &pad_cfg, &sd_format);
> +	if (ret)
> +		return ret;
> +
> +	/* Scale down to sensor supported sizes */
> +	if (sd_format.format.width != pix->width ||
> +	    sd_format.format.height != pix->height) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Unable to apply: 0x%x - %ux%u - scale to %ux%u\n",
> +			 pix->pixelformat, pix->width, pix->height,
> +			 sd_format.format.width, sd_format.format.height);

This shouldn't print an error message to the kernel log either.

> +		pix->width = sd_format.format.width;
> +		pix->height = sd_format.format.height;
> +	}

No need for a conditional assignment, you can just write

	/* The CEU can't scale. */
	pix->width = sd_format.format.width;
	pix->height = sd_format.format.height;

> +
> +	/* Calculate per-plane sizes based on image format */
> +	v4l2_fill_pix_format_mplane(pix, &sd_format.format);
> +	pix->field = V4L2_FIELD_NONE;
> +	ret = ceu_calc_plane_sizes(ceudev, ceu_fmt, pix);
> +	if (ret < 0)
> +		return ret;
> +
> +	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_subdev *ceu_sd = ceudev->sd;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	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_mplane(&format.format, &v4l2_fmt->fmt.pix_mp);
> +	ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, NULL, &format);
> +	if (ret)
> +		return ret;
> +
> +	ceudev->v4l2_pix = v4l2_fmt->fmt.pix_mp;
> +
> +	ret = ceu_set_bus_params(ceudev);

Do you need to do this here, can't you program the registers once at streamon 
time only ?

> +	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_MPLANE,
> +		.fmt.pix_mp = {
> +			.width		= VGA_WIDTH,
> +			.height		= VGA_HEIGHT,
> +			.field		= V4L2_FIELD_NONE,
> +			.pixelformat	= V4L2_PIX_FMT_NV16,
> +			.plane_fmt	= {
> +				[0]	= {
> +					.sizeimage = VGA_WIDTH * VGA_HEIGHT * 2,
> +					.bytesperline = VGA_WIDTH * 2,
> +				},
> +				[1]	= {
> +					.sizeimage = VGA_WIDTH * VGA_HEIGHT * 2,
> +					.bytesperline = VGA_WIDTH * 2,
> +				},
> +			},
> +		},
> +	};
> +
> +	ret = ceu_try_fmt(ceudev, &v4l2_fmt);
> +	if (ret)
> +		return ret;

I would assume the default values not to generate an error.

> +	ceudev->v4l2_pix = v4l2_fmt.fmt.pix_mp;
> +
> +	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;
> + * - YUYV422 if sensor provides YUYV422
> + *
> + * TODO: Other YUYV422 permutations throug data fetch sync mode and DTARY

s/throug/through/

> + * TODO: Binary data (eg. JPEG) and raw formats through data fetch sync
> mode
> + */
> +static int ceu_init_formats(struct ceu_device *ceudev)
> +{
> +	struct ceu_subdev *ceu_sd = ceudev->sd;
> +	struct ceu_mbus_fmt *mbus_fmt = &ceu_sd->mbus_fmt;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	bool yuyv_bus_fmt = false;
> +
> +	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 YUYV422 */
> +	while (!yuyv_bus_fmt &&
> +	       !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:
> +		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;
> +		}
> +
> +		sd_mbus_fmt.index++;
> +	}
> +
> +	if (!yuyv_bus_fmt)
> +		return -ENXIO;
> +
> +	/*
> +	 * Save the first encountered YUYV format as "mbus_fmt" and use it
> +	 * to output all planar YUV422 and YUV420 (NV*) formats to memory as
> +	 * well as for data synch fetch mode (YUYV - YVYU etc. ).
> +	 */
> +	mbus_fmt->mbus_code	= sd_mbus_fmt.code;
> +	mbus_fmt->bps		= 8;
> +
> +	/* Annotate the selected bus format components ordering */
> +	switch (sd_mbus_fmt.code) {
> +	case MEDIA_BUS_FMT_YUYV8_2X8:
> +		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_YUYV;
> +		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_YVYU;
> +		mbus_fmt->swapped		= false;
> +		mbus_fmt->bpp			= 16;
> +		break;
> +
> +	case MEDIA_BUS_FMT_YVYU8_2X8:
> +		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_YVYU;
> +		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_YUYV;
> +		mbus_fmt->swapped		= true;
> +		mbus_fmt->bpp			= 16;
> +		break;
> +
> +	case MEDIA_BUS_FMT_UYVY8_2X8:
> +		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_UYVY;
> +		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_VYUY;
> +		mbus_fmt->swapped		= false;
> +		mbus_fmt->bpp			= 16;
> +		break;
> +
> +	case MEDIA_BUS_FMT_VYUY8_2X8:
> +		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_VYUY;
> +		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_UYVY;
> +		mbus_fmt->swapped		= true;
> +		mbus_fmt->bpp			= 16;
> +		break;
> +	}
> +
> +	ceudev->field = V4L2_FIELD_NONE;
> +
> +	return 0;
> +}
> +
> +/**

Is this really kerneldoc ?

> + * ------------------------------------------------------------------------
> + *  Runtime PM Handlers
> + */

[snip]

> +/**

Ditto.

> + * ------------------------------------------------------------------------
> + *  File Operations
> + */

[snip]

> +/**

Ditto.

> + * -----------------------------------------------------------------------
> + *  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));

Shouldn't this distinguish between multiple instances of the same device ?

> +
> +	return 0;
> +}

[snip]

> +static int ceu_enum_input(struct file *file, void *priv,
> +			  struct v4l2_input *inp)
> +{
> +	if (inp->index != 0)
> +		return -EINVAL;

Doesn't the driver support two inputs ?

> +	inp->type = V4L2_INPUT_TYPE_CAMERA;
> +	inp->std = 0;
> +	strcpy(inp->name, "Camera");

Shouldn't this reflect the name of the input ?

> +
> +	return 0;
> +}
> +
> +static int ceu_g_input(struct file *file, void *priv, unsigned int *i)
> +{
> +	struct ceu_device *ceudev = video_drvdata(file);
> +
> +	*i = ceudev->sd_index;
> +
> +	return 0;
> +}
> +
> +static int ceu_s_input(struct file *file, void *priv, unsigned int i)
> +{
> +	struct ceu_device *ceudev = video_drvdata(file);
> +	struct ceu_subdev *ceu_sd_old;
> +	int ret;
> +
> +	if (i >= ceudev->num_sd)
> +		return -EINVAL;
> +
> +	ceu_sd_old = ceudev->sd;
> +	ceudev->sd = &ceudev->subdevs[i];
> +
> +	/* Make sure we can generate output image formats. */
> +	ret = ceu_init_formats(ceudev);
> +	if (ret) {
> +		ceudev->sd = 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_sd, core, s_power, 0);
> +	v4l2_subdev_call(ceudev->sd->v4l2_sd, core, s_power, 1);

What if this function is called while streaming ?

> +	ceudev->sd_index = i;
> +
> +	return 0;
> +}

[snip]

> +static int ceu_sensor_bound(struct v4l2_async_notifier *notifier,
> +			    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(asd);
> +
> +	if (video_is_registered(&ceudev->vdev)) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Video device registered before this sub-device.\n");
> +		return -EBUSY;

Can this happen ?

> +	}
> +
> +	/* Assign subdevices in the order they appear */
> +	ceu_sd->v4l2_sd = v4l2_sd;
> +	ceudev->num_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 */
> +	q->type			= V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> +	q->io_modes		= VB2_MMAP | VB2_USERPTR;

No dmabuf ?

> +	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;

[snip]

> +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 num_sd;
> +	int ret;
> +
> +	ceudev = kzalloc(sizeof(*ceudev), GFP_KERNEL);

The memory is freed in ceu_vdev_release() as expected, but that will only work 
if the video device is registered. If the subdevs are never bound, the ceudev 
memory will be leaked if you unbind the CEU device from its driver. In my 
opinion this calls for registering the video device at probe time (although 
Hans disagrees).

> +	if (!ceudev)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, ceudev);
> +	dev_set_drvdata(dev, ceudev);

You don't need the second line, platform_set_drvdata() is a wrapper around 
dev_set_drvdata().

> +	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);
> +	if (IS_ERR(res))
> +		return PTR_ERR(res);

No need for error handling here, devm_ioremap_resource() will check the res 
pointer.

> +	base = devm_ioremap_resource(dev, res);

You can assign ceudev->base directly and remove the base local variable.

> +	if (IS_ERR(base))
> +		return PTR_ERR(base);
> +	ceudev->base = base;
> +
> +	ret = platform_get_irq(pdev, 0);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to get irq: %d\n", ret);
> +		return ret;
> +	}
> +	irq = ret;
> +
> +	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;
> +	}
> +
> +	pm_suspend_ignore_children(dev, true);
> +	pm_runtime_enable(dev);
> +
> +	ret = v4l2_device_register(dev, &ceudev->v4l2_dev);
> +	if (ret)
> +		goto error_pm_disable;
> +
> +	if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
> +		num_sd = ceu_parse_dt(ceudev);
> +	} else if (dev->platform_data) {
> +		num_sd = ceu_parse_platform_data(ceudev, dev->platform_data);
> +	} else {
> +		dev_err(dev, "CEU platform data not set and no OF support\n");
> +		ret = -EINVAL;
> +		goto error_v4l2_unregister;
> +	}
> +
> +	if (num_sd < 0) {
> +		ret = num_sd;
> +		goto error_v4l2_unregister;
> +	} else if (num_sd == 0)
> +		return 0;

You need braces around the second statement too.

[snip]

> +static const struct dev_pm_ops ceu_pm_ops = {
> +	SET_RUNTIME_PM_OPS(ceu_runtime_suspend,
> +			   ceu_runtime_resume,
> +			   NULL)

You'll probably need system PM ops eventually, but for now this isn't a 
regression so I won't complain too much.

> +};

[snip]

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 05/10] arch: sh: migor: Use new renesas-ceu camera driver
  2017-11-15 10:55 ` [PATCH v1 05/10] arch: sh: migor: Use new renesas-ceu camera driver Jacopo Mondi
  2017-12-11 14:36   ` Laurent Pinchart
@ 2017-12-12 10:00   ` Laurent Pinchart
  1 sibling, 0 replies; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-12 10:00 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: magnus.damm, geert, mchehab, hverkuil, linux-renesas-soc,
	linux-media, linux-sh, linux-kernel

Hi Jacopo,

Thank you for the patch.

On Wednesday, 15 November 2017 12:55:58 EET Jacopo Mondi wrote:
> Migo-R platform uses sh_mobile_ceu camera driver, which is now being
> replaced by a proper V4L2 camera driver named 'renesas-ceu'.
> 
> Move Migo-R platform to use the v4l2 renesas-ceu camera driver
> interface and get rid of soc_camera defined components used to register
> sensor drivers.
> 
> Also, memory for CEU video buffers is now reserved with membocks APIs,
> and need to be declared as dma_coherent during machine initialization to
> remove that architecture specific part from CEU driver.
> 
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  arch/sh/boards/mach-migor/setup.c | 164 ++++++++++++++++------------------
>  1 file changed, 76 insertions(+), 88 deletions(-)
> 
> diff --git a/arch/sh/boards/mach-migor/setup.c
> b/arch/sh/boards/mach-migor/setup.c index 98aa094..10a9b3c 100644
> --- a/arch/sh/boards/mach-migor/setup.c
> +++ b/arch/sh/boards/mach-migor/setup.c
> @@ -27,7 +27,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>
> @@ -308,62 +308,80 @@ 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.
>  	 */
>  	camera_clk = clk_get(NULL, "video_clk");
>  	clk_set_rate(camera_clk, 10000000);
>  	clk_enable(camera_clk);	/* start VIO_CKO */
> -
> -	/* 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)
> +static void camera_disable(void)
>  {
> -	clk_disable(camera_clk); /* stop VIO_CKO */
> +	/* stop VIO_CKO */
> +	clk_disable(camera_clk);
>  	clk_put(camera_clk);
> 
> +	gpio_set_value(GPIO_PTT0, 0);
> +	gpio_set_value(GPIO_PTT2, 1);
>  	gpio_set_value(GPIO_PTT3, 0);
> +
>  	mutex_unlock(&camera_lock);
>  }
> 
> -static int ov7725_power(struct device *dev, int mode)
> +static void camera_reset(void)
>  {
> -	if (mode)
> -		camera_power_on(0);
> -	else
> -		camera_power_off();
> +	/* use VIO_RST to take camera out of reset */
> +	gpio_set_value(GPIO_PTT3, 0);
> +	mdelay(10);
> +	gpio_set_value(GPIO_PTT3, 1);
> +	mdelay(10);
> +}
> +
> +static int ov7725_enable(void)
> +{
> +	mutex_lock(&camera_lock);
> +	camera_vio_clk_on();
> +	mdelay(10);
> +	gpio_set_value(GPIO_PTT0, 1);
> +
> +	camera_reset();
> 
>  	return 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();
> +	mutex_lock(&camera_lock);
> +	camera_vio_clk_on();
> +	mdelay(10);
> +	gpio_set_value(GPIO_PTT2, 0);
> +
> +	camera_reset();
> 
>  	return 0;
>  }

After studying the schematics, and if you can confirm that R26 is not mounted 
on the panel board, I think all this could be handled by the OV7720 and TW9910 
drivers.

The clock is easy, it's used by the OV7720 only, just expose it as the input 
clock for that device. On a side note, your ov772x driver in this series tries 
to get a clock named "mclk", but it should be named "xclk" as that's how the 
chip's input signal is named. The TW9910 has its own crystal oscillator so it 
won't be affected. As a bonus point the clock will remain off when capturing 
from the TW9910.

The TV_IN_EN and CAM_EN signals (PTT2 and PTT0 GPIOs respectively) shouldn't 
be difficult either. You can expose them as the PDN and PWDN GPIOs for the 
OV7720 and TW9910 and handle them in the respective drivers. CAM_EN also 
controls the digital video bus multiplexing, and unless I'm mistaken it will 
just work as a side effect of power down signal control as long as you make 
sure that the OV7720 remains in power down when not selected as the CEU input.

The VIO_RST signal (PTT3 GPIO) is a bit more annoying, as it is shared by both 
the OV7720 and TW9910 as their reset signal, and as far as I know GPIOs can't 
be easily shared between drivers.

Using a reset controller might be an option but I can't see any reset 
controller driver for GPIOs. https://patchwork.kernel.org/patch/3978091/ seems 
not to have been merged. This being said, having to instantiate a reset 
controller to share a GPIO is annoying, I wonder if it would make sense to add 
support for shared GPIOs in the GPIO core.

Another option could be to only request the reset GPIO when needed instead of 
doing so at probe time.

> -static struct sh_mobile_ceu_info sh_mobile_ceu_info = {
> -	.flags = SH_CEU_FLAG_USE_8BIT_BUS,
> +static struct ceu_info ceu_info = {
> +	.num_subdevs		= 2,
> +	.subdevs = {
> +		{ /* [0] = ov772x */
> +			.flags		= CEU_FLAG_PRIMARY_SENS,
> +			.bus_width	= 8,
> +			.bus_shift	= 0,
> +			.i2c_adapter_id	= 0,
> +			.i2c_address	= 0x21,
> +		},
> +		{ /* [1] = tw9910 */
> +			.flags		= 0,
> +			.bus_width	= 8,
> +			.bus_shift	= 0,
> +			.i2c_adapter_id	= 0,
> +			.i2c_address	= 0x45,
> +		},
> +	},
>  };
> 
>  static struct resource migor_ceu_resources[] = {
> @@ -377,18 +395,15 @@ static struct resource migor_ceu_resources[] = {
>  		.start  = evt2irq(0x880),
>  		.flags  = IORESOURCE_IRQ,
>  	},
> -	[2] = {
> -		/* place holder for contiguous memory */
> -	},
>  };
> 
>  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,
>  	},
>  };
> 
> @@ -427,6 +442,19 @@ static struct platform_device sdhi_cn9_device = {
>  	},
>  };
> 
> +static struct ov772x_camera_info ov7725_info = {
> +	.platform_enable = ov7725_enable,
> +	.platform_disable = camera_disable,
> +};
> +
> +static struct tw9910_video_info tw9910_info = {
> +	.buswidth       = TW9910_DATAWIDTH_8,
> +	.mpout          = TW9910_MPO_FIELD,
> +
> +	.platform_enable = tw9910_enable,
> +	.platform_disable = camera_disable,
> +};
> +
>  static struct i2c_board_info migor_i2c_devices[] = {
>  	{
>  		I2C_BOARD_INFO("rs5c372b", 0x32),
> @@ -438,51 +466,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,12 +480,9 @@ static struct platform_device *migor_devices[]
> __initdata = { &smc91x_eth_device,
>  	&sh_keysc_device,
>  	&migor_lcdc_device,
> -	&migor_ceu_device,
>  	&migor_nor_flash_device,
>  	&migor_nand_flash_device,
>  	&sdhi_cn9_device,
> -	&migor_camera[0],
> -	&migor_camera[1],
>  };
> 
>  extern char migor_sdram_enter_start;
> @@ -505,8 +492,6 @@ extern char migor_sdram_leave_end;
> 
>  static int __init migor_devices_setup(void)
>  {
> -	struct resource *r;
> -
>  	/* register board specific self-refresh code */
>  	sh_mobile_register_self_refresh(SUSP_SH_STANDBY | SUSP_SH_SF,
>  					&migor_sdram_enter_start,
> @@ -651,16 +636,19 @@ static int __init migor_devices_setup(void)
>  	 */
>  	__raw_writew(__raw_readw(PORT_MSELCRA) | 1, PORT_MSELCRA);
> 
> -	/* Setup additional memory resource for CEU video buffers */
> -	r = &migor_ceu_device.resource[2];
> -	r->flags = IORESOURCE_MEM;
> -	r->start = ceu_dma_membase;
> -	r->end = r->start + CEU_BUFFER_MEMORY_SIZE - 1;
> -	r->name = "ceu";
> -
>  	i2c_register_board_info(0, migor_i2c_devices,
>  				ARRAY_SIZE(migor_i2c_devices));
> 
> +	/* Initialize CEU platform device separately to map memory first */
> +	device_initialize(&migor_ceu_device.dev);
> +	arch_setup_pdev_archdata(&migor_ceu_device);
> +	dma_declare_coherent_memory(&migor_ceu_device.dev,
> +				    ceu_dma_membase, ceu_dma_membase,
> +				    ceu_dma_membase + CEU_BUFFER_MEMORY_SIZE - 1,
> +				    DMA_MEMORY_EXCLUSIVE);
> +
> +	platform_device_add(&migor_ceu_device);
> +
>  	return platform_add_devices(migor_devices, ARRAY_SIZE(migor_devices));
>  }
>  arch_initcall(migor_devices_setup);

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-11-15 10:55 ` [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver Jacopo Mondi
  2017-11-15 12:45   ` Sakari Ailus
  2017-12-11 16:15   ` Laurent Pinchart
@ 2017-12-13 12:03   ` Hans Verkuil
  2017-12-18 14:12     ` jacopo mondi
  2 siblings, 1 reply; 56+ messages in thread
From: Hans Verkuil @ 2017-12-13 12:03 UTC (permalink / raw)
  To: Jacopo Mondi, laurent.pinchart, magnus.damm, geert, mchehab
  Cc: linux-renesas-soc, linux-media, linux-sh, linux-kernel

On 15/11/17 11:55, Jacopo Mondi wrote:
> Add driver for Renesas Capture Engine Unit (CEU).
> 
> The CEU interface supports capturing 'data' (YUV422) and 'images'
> (NV[12|21|16|61]).
> 
> This driver aims to replace the soc_camera based sh_mobile_ceu one.
> 
> Tested with ov7670 camera sensor, providing YUYV_2X8 data on Renesas RZ
> platform GR-Peach.
> 
> Tested with ov7725 camera sensor on SH4 platform Migo-R.
> 
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  drivers/media/platform/Kconfig       |    9 +
>  drivers/media/platform/Makefile      |    2 +
>  drivers/media/platform/renesas-ceu.c | 1768 ++++++++++++++++++++++++++++++++++
>  3 files changed, 1779 insertions(+)
>  create mode 100644 drivers/media/platform/renesas-ceu.c
> 
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 3c4f7fa..401caea 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -144,6 +144,15 @@ config VIDEO_STM32_DCMI
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called stm32-dcmi.
> 
> +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
> +	select V4L2_FWNODE
> +	---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 327f80a..0d1f02b 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -27,6 +27,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_MUX)			+= video-mux.o
> diff --git a/drivers/media/platform/renesas-ceu.c b/drivers/media/platform/renesas-ceu.c
> new file mode 100644
> index 0000000..aaba3cd
> --- /dev/null
> +++ b/drivers/media/platform/renesas-ceu.c
> @@ -0,0 +1,1768 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * V4L2 Driver for Renesas Capture Engine Unit (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/of_graph.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-fwnode.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-image-sizes.h>
> +#include <media/v4l2-mediabus.h>
> +#include <media/videobuf2-dma-contig.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_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_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_CFWCR	0x5c /* Firewall operation control register	*/
> +#define CEU_CDOCR	0x64 /* Capture data output control 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		*/
> +
> +/* Data synchronous fetch mode */
> +#define CEU_CAMCR_JPEG			BIT(4)
> +
> +/* Input components ordering: CEU_CAMCR.DTARY field */
> +#define CEU_CAMCR_DTARY_8_UYVY		(0x00 << 8)
> +#define CEU_CAMCR_DTARY_8_VYUY		(0x01 << 8)
> +#define CEU_CAMCR_DTARY_8_YUYV		(0x02 << 8)
> +#define CEU_CAMCR_DTARY_8_YVYU		(0x03 << 8)
> +/* TODO: input components ordering for 16 bits input */
> +
> +/* Bus transfer MTU */
> +#define CEU_CAPCR_BUS_WIDTH256		(0x3 << 20)
> +
> +/* Bus width configuration */
> +#define CEU_CAMCR_DTIF_16BITS		BIT(12)
> +
> +/* No downsampling to planar YUV420 in image fetch mode */
> +#define CEU_CDOCR_NO_DOWSAMPLE		BIT(4)
> +
> +/* Swap all input data in 8-bit, 16-bits and 32-bits units (Figure 46.45) */
> +#define CEU_CDOCR_SWAP_ENDIANNESS	(7)
> +
> +/* Capture reset and enable bits */
> +#define CEU_CAPSR_CPKIL			BIT(16)
> +#define CEU_CAPSR_CE			BIT(0)
> +
> +/* CEU operating flag bit */
> +#define CEU_CAPCR_CTNCP			BIT(16)
> +#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)
> +#define CEU_CEIER_MASK			(CEU_CEIER_CPE | CEU_CEIER_VBP)
> +
> +/* mbus configuration flags */
> +#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
> + * @bpp: number of bits per pixels unit
> + */
> +struct ceu_mbus_fmt {
> +	u32	mbus_code;
> +	u32	fmt_order;
> +	u32	fmt_order_swap;
> +	bool	swapped;
> +	u8	bps;
> +	u8	bpp;
> +};
> +
> +/**
> + * ceu_buffer - Link vb2 buffer to the list of available 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_subdev - Wraps v4l2 sub-device and provides async subdevice.
> + */
> +struct ceu_subdev {
> +	struct v4l2_subdev *v4l2_sd;
> +	struct v4l2_async_subdev asd;
> +
> +	/* per-subdevice mbus configuration options */
> +	unsigned int mbus_flags;
> +	struct ceu_mbus_fmt mbus_fmt;
> +};
> +
> +static struct ceu_subdev *to_ceu_subdev(struct v4l2_async_subdev *asd)
> +{
> +	return container_of(asd, struct ceu_subdev, asd);
> +}
> +
> +/**
> + * ceu_device - CEU device instance
> + */
> +struct ceu_device {
> +	struct device		*dev;
> +	struct video_device	vdev;
> +	struct v4l2_device	v4l2_dev;
> +
> +	/* subdevices descriptors */
> +	struct ceu_subdev	*subdevs;
> +	/* the subdevice currently in use */
> +	struct ceu_subdev	*sd;
> +	unsigned int		sd_index;
> +	unsigned int		num_sd;
> +
> +	/* currently configured field and pixel format */
> +	enum v4l2_field	field;
> +	struct v4l2_pix_format_mplane v4l2_pix;
> +
> +	/* async subdev notification helpers */
> +	struct v4l2_async_notifier notifier;
> +	/* pointers to "struct ceu_subdevice -> asd" */
> +	struct v4l2_async_subdev **asds;
> +
> +	/* 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;
> +
> +	/* base - CEU memory base address */
> +	void __iomem	*base;
> +};
> +
> +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
> + * capabilities.
> + */
> +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,
> +	},
> +	{
> +		.fourcc	= V4L2_PIX_FMT_YUYV,
> +		.bpp	= 16,
> +	},
> +};
> +
> +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;
> +}
> +
> +static inline bool ceu_is_fmt_planar(struct v4l2_pix_format_mplane *pix)
> +{
> +	switch (pix->pixelformat) {
> +	case V4L2_PIX_FMT_YUYV:
> +		return false;
> +	case V4L2_PIX_FMT_NV16:
> +	case V4L2_PIX_FMT_NV61:
> +	case V4L2_PIX_FMT_NV12:
> +	case V4L2_PIX_FMT_NV21:
> +		return true;
> +	}
> +
> +	return true;
> +}
> +
> +/* ----------------------------------------------------------------------------
> + * 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
> + */
> +static int ceu_soft_reset(struct ceu_device *ceudev)
> +{
> +	unsigned int reset_done;
> +	unsigned int i;
> +
> +	ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CPKIL);
> +
> +	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) {
> +		v4l2_err(&ceudev->v4l2_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 capture.
> + */
> +static int ceu_capture(struct ceu_device *ceudev)
> +{
> +	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> +	dma_addr_t phys_addr_top;
> +	u32 status;
> +
> +	/* Clean interrupt status and re-enable interrupts */
> +	status = ceu_read(ceudev, CEU_CETCR);
> +	ceu_write(ceudev, CEU_CEIER,
> +		  ceu_read(ceudev, CEU_CEIER) & ~CEU_CEIER_MASK);
> +	ceu_write(ceudev, CEU_CETCR, ~status & CEU_CETCR_MAGIC);
> +	ceu_write(ceudev, CEU_CEIER, CEU_CEIER_MASK);
> +	ceu_write(ceudev, CEU_CAPCR,
> +		  ceu_read(ceudev, CEU_CAPCR) & ~CEU_CAPCR_CTNCP);
> +
> +	/*
> +	 * 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) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "VBP interrupt while capturing\n");
> +		ceu_soft_reset(ceudev);
> +		return -EIO;
> +	} else if (!ceudev->active) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "No available buffers for capture\n");
> +		return -EINVAL;
> +	}
> +
> +	phys_addr_top =
> +		vb2_dma_contig_plane_dma_addr(&ceudev->active->vb2_buf, 0);
> +	ceu_write(ceudev, CEU_CDAYR, phys_addr_top);
> +
> +	/* Ignore CbCr plane in data sync mode */
> +	if (ceu_is_fmt_planar(pix)) {
> +		phys_addr_top =
> +			vb2_dma_contig_plane_dma_addr(&ceudev->active->vb2_buf,
> +						      1);
> +		ceu_write(ceudev, CEU_CDACR, phys_addr_top);
> +	}
> +
> +	/*
> +	 * 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 0;
> +}
> +
> +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;
> +
> +	/* Prepare a new 'active' buffer and trigger a new capture */
> +	buf = vb2_to_ceu(vbuf);
> +	vbuf->vb2_buf.timestamp = ktime_get_ns();
> +
> +	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;
> +	}
> +
> +	/*
> +	 * If the new capture started successfully, mark the previous buffer
> +	 * as "DONE".
> +	 */
> +	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_calc_plane_sizes() - Fill 'struct v4l2_plane_pix_format' per plane
> + *			    information according to the currently configured
> + *			    pixel format.
> + */
> +static int ceu_calc_plane_sizes(struct ceu_device *ceudev,
> +				const struct ceu_fmt *ceu_fmt,
> +				struct v4l2_pix_format_mplane *pix)
> +{
> +	struct v4l2_plane_pix_format *plane_fmt = &pix->plane_fmt[0];
> +
> +	switch (pix->pixelformat) {
> +	case V4L2_PIX_FMT_YUYV:
> +		pix->num_planes			= 1;
> +		plane_fmt[0].bytesperline	= pix->width * ceu_fmt->bpp / 8;
> +		plane_fmt[0].sizeimage		= pix->height *
> +						  plane_fmt[0].bytesperline;
> +		break;
> +	case V4L2_PIX_FMT_NV16:
> +	case V4L2_PIX_FMT_NV61:
> +		pix->num_planes			= 2;
> +		plane_fmt[0].bytesperline	= pix->width;
> +		plane_fmt[0].sizeimage		= pix->height * pix->width;
> +		plane_fmt[1]			= plane_fmt[0];
> +		break;
> +	case V4L2_PIX_FMT_NV12:
> +	case V4L2_PIX_FMT_NV21:
> +		pix->num_planes			= 2;
> +		plane_fmt[0].bytesperline	= pix->width;
> +		plane_fmt[0].sizeimage		= pix->height * pix->width;
> +		plane_fmt[1].bytesperline	= pix->width;
> +		plane_fmt[1].sizeimage		= pix->height * pix->width / 2;
> +		break;
> +	default:
> +		pix->num_planes			= 0;
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Format 0x%x not supported\n", pix->pixelformat);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +/*
> + * 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,

A general request: can you replace all videobuf_ strings by vb2_?

'videobuf' generally refers to the first version of the videobuf framework (and
in fact, that's what soc_camera originally used), but I'd rather rename it here
so a grep on videobuf won't hit this driver.

> +			      unsigned int *num_planes, unsigned int sizes[],
> +			      struct device *alloc_devs[])
> +{
> +	struct ceu_device *ceudev = vb2_get_drv_priv(vq);
> +	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> +	unsigned int i;
> +
> +	if (!vq->num_buffers)
> +		ceudev->sequence = 0;
> +
> +	if (!*count)
> +		*count = 2;
> +
> +	/* num_planes is set: just check plane sizes */
> +	if (*num_planes) {
> +		for (i = 0; i < pix->num_planes; i++) {
> +			if (sizes[i] < pix->plane_fmt[i].sizeimage)
> +				return -EINVAL;
> +		}
> +
> +		return 0;
> +	}
> +
> +	/* num_planes not set: called from REQBUFS, just set plane sizes */
> +	*num_planes = pix->num_planes;
> +	for (i = 0; i < pix->num_planes; i++)
> +		sizes[i] = pix->plane_fmt[i].sizeimage;
> +
> +	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 v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> +	struct ceu_buffer *buf = vb2_to_ceu(vbuf);
> +	unsigned long irqflags;
> +	unsigned int i;
> +
> +	for (i = 0; i < pix->num_planes; i++) {
> +		if (vb2_plane_size(vb, i) < pix->plane_fmt[i].sizeimage) {
> +			v4l2_err(&ceudev->v4l2_dev,
> +				 "Buffer #%d too small (%lu < %u)\n",
> +				 vb->index, vb2_plane_size(vb, i),
> +				 pix->plane_fmt[i].sizeimage);
> +			goto error;
> +		}
> +
> +		vb2_set_plane_payload(vb, i, pix->plane_fmt[i].sizeimage);
> +	}
> +
> +	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 v4l2_subdev *v4l2_sd = ceudev->sd->v4l2_sd;
> +	struct ceu_buffer *buf;
> +	unsigned long irqflags;
> +	int ret = 0;
> +
> +	ret = v4l2_subdev_call(v4l2_sd, video, s_stream, 1);
> +	if (ret && ret != -ENOIOCTLCMD) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Subdevice failed to start streaming: %d\n", ret);
> +		goto error_return_bufs;
> +	}
> +
> +	spin_lock_irqsave(&ceudev->lock, irqflags);
> +	ceudev->sequence = 0;
> +
> +	if (ceudev->active) {
> +		ret = -EINVAL;
> +		spin_unlock_irqrestore(&ceudev->lock, irqflags);
> +		goto error_stop_sensor;
> +	}
> +
> +	/* Grab the first available buffer and trigger the first capture. */
> +	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(v4l2_sd, 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 v4l2_subdev *v4l2_sd = ceudev->sd->v4l2_sd;
> +	struct ceu_buffer *buf;
> +	unsigned long irqflags;
> +
> +	v4l2_subdev_call(v4l2_sd, 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(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
> +	INIT_LIST_HEAD(&ceudev->capture);
> +
> +	spin_unlock_irqrestore(&ceudev->lock, irqflags);
> +
> +	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 errors
> + *	    0 if g_mbus_config is not supported,
> + *	    > 0  for bus configuration flags supported by (ceu AND sensor)
> + */
> +static int ceu_test_mbus_param(struct ceu_device *ceudev)
> +{
> +	struct v4l2_subdev *sd = ceudev->sd->v4l2_sd;
> +	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 at all possible, don't call this. This information should just come from
the device tree.

I want to eventually remove the g/s_mbus_config, so I'd rather not keep it here.
The soc_camera driver is one of the very few drivers that use it (the other is
pxa_camera).

> +	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_bus_params() - Configure CEU interface registers using bus
> + *			  parameters
> + */
> +static int ceu_set_bus_params(struct ceu_device *ceudev)
> +{
> +	u32 camcr = 0, cdocr = 0, cfzsr = 0, cdwdr = 0, capwr = 0;
> +	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> +	struct ceu_subdev *ceu_sd = ceudev->sd;
> +	struct ceu_mbus_fmt *mbus_fmt = &ceu_sd->mbus_fmt;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	unsigned int mbus_flags = ceu_sd->mbus_flags;
> +	unsigned long common_flags = CEU_BUS_FLAGS;
> +	int ret;
> +	struct v4l2_mbus_config cfg = {
> +		.type = V4L2_MBUS_PARALLEL,
> +	};
> +
> +	/*
> +	 * If client doesn't implement g_mbus_config, we just use our
> +	 * platform data.
> +	 */
> +	ret = ceu_test_mbus_param(ceudev);
> +	if (ret < 0)
> +		return ret;
> +	else if (ret == 0)
> +		common_flags = ceudev->sd->mbus_flags;
> +	else
> +		common_flags = ret;
> +
> +	/*
> +	 * If the we can choose between multiple alternatives select
> +	 * active high polarities.
> +	 */
> +	if ((common_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) &&
> +	    (common_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) {
> +		if (mbus_flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
> +			common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_LOW;
> +		else
> +			common_flags &= ~V4L2_MBUS_HSYNC_ACTIVE_HIGH;
> +	}
> +
> +	if ((common_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) &&
> +	    (common_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) {
> +		if (mbus_flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
> +			common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_LOW;
> +		else
> +			common_flags &= ~V4L2_MBUS_VSYNC_ACTIVE_HIGH;
> +	}
> +
> +	cfg.flags = common_flags;
> +	ret = v4l2_subdev_call(v4l2_sd, video, s_mbus_config, &cfg);
> +	if (ret < 0 && ret != -ENOIOCTLCMD)
> +		return ret;
> +
> +	/* Start configuring CEU registers */
> +	ceu_write(ceudev, CEU_CAIFR, 0);
> +	ceu_write(ceudev, CEU_CFWCR, 0);
> +	ceu_write(ceudev, CEU_CRCNTR, 0);
> +	ceu_write(ceudev, CEU_CRCMPR, 0);
> +
> +	/* Set the frame capture period for both image capture and data sync */
> +	capwr = (pix->height << 16) | pix->width * mbus_fmt->bpp / 8;
> +
> +	/*
> +	 * Swap input data endianness by default.
> +	 * 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)
> +	 *
> +	 * Use CEU_CDOCR[2:0] to swap data ordering.
> +	 */
> +	cdocr = CEU_CDOCR_SWAP_ENDIANNESS;
> +
> +	/*
> +	 * Configure CAMCR and CDOCR:
> +	 * match input components ordering with memory output format and
> +	 * handle downsampling to YUV420.
> +	 *
> +	 * If the memory output planar format is 'swapped' (Cr before Cb) and
> +	 * input format is not, use the swapped version of CAMCR.DTARY.
> +	 *
> +	 * If the memory output planar 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
> +	 *
> +	 * No downsample for data fetch sync mode.
> +	 */
> +	switch (pix->pixelformat) {
> +	/* data fetch sync mode */
> +	case V4L2_PIX_FMT_YUYV:
> +		/* TODO: handle YUYV permutations through DTARY bits */
> +		camcr	|= CEU_CAMCR_JPEG;
> +		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
> +		cfzsr	= (pix->height << 16) | pix->width;
> +		cdwdr	= pix->plane_fmt[0].bytesperline;
> +		break;
> +
> +	/* non-swapped planar image capture mode */
> +	case V4L2_PIX_FMT_NV16:
> +		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
> +	case V4L2_PIX_FMT_NV12:
> +		if (mbus_fmt->swapped)
> +			camcr |= mbus_fmt->fmt_order_swap;
> +		else
> +			camcr |= mbus_fmt->fmt_order;
> +
> +		cfzsr	= (pix->height << 16) | pix->width;
> +		cdwdr	= pix->width;
> +		break;
> +
> +	/* swapped planar image capture mode */
> +	case V4L2_PIX_FMT_NV61:
> +		cdocr	|= CEU_CDOCR_NO_DOWSAMPLE;
> +	case V4L2_PIX_FMT_NV21:
> +		if (mbus_fmt->swapped)
> +			camcr |= mbus_fmt->fmt_order;
> +		else
> +			camcr |= mbus_fmt->fmt_order_swap;
> +
> +		cfzsr	= (pix->height << 16) | pix->width;
> +		cdwdr	= pix->width;
> +		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 with DTIF bit in CAMCR */
> +	ceu_write(ceudev, CEU_CAMCR, camcr);
> +	ceu_write(ceudev, CEU_CDOCR, cdocr);
> +	ceu_write(ceudev, CEU_CAPCR, CEU_CAPCR_BUS_WIDTH256);
> +
> +	/*
> +	 * TODO: make CAMOR offsets configurable.
> +	 * CAMOR wants to know the number of blanks between a VS/HS signal
> +	 * and valid data. This value should actually come from the sensor...
> +	 */
> +	ceu_write(ceudev, CEU_CAMOR, 0);
> +
> +	/* TODO: 16 bit bus width require re-calculation of cdwdr and cfzsr */
> +	ceu_write(ceudev, CEU_CAPWR, capwr);
> +	ceu_write(ceudev, CEU_CFSZR, cfzsr);
> +	ceu_write(ceudev, CEU_CDWDR, cdwdr);
> +
> +	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 v4l2_pix_format_mplane *pix = &v4l2_fmt->fmt.pix_mp;
> +	struct ceu_subdev *ceu_sd = ceudev->sd;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	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_YUYV:
> +	case V4L2_PIX_FMT_NV16:
> +	case V4L2_PIX_FMT_NV61:
> +	case V4L2_PIX_FMT_NV12:
> +	case V4L2_PIX_FMT_NV21:
> +		break;
> +
> +	default:
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Pixel format 0x%x not supported, default to NV16\n",
> +			 pix->pixelformat);
> +		pix->pixelformat = V4L2_PIX_FMT_NV16;
> +	}
> +
> +	ceu_fmt = get_ceu_fmt_from_fourcc(pix->pixelformat);
> +
> +	/* 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 used to produce memory
> +	 * format is selected at initialization time
> +	 */
> +	v4l2_fill_mbus_format_mplane(&sd_format.format, pix);
> +	ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, &pad_cfg, &sd_format);
> +	if (ret)
> +		return ret;
> +
> +	/* Scale down to sensor supported sizes */
> +	if (sd_format.format.width != pix->width ||
> +	    sd_format.format.height != pix->height) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Unable to apply: 0x%x - %ux%u - scale to %ux%u\n",
> +			 pix->pixelformat, pix->width, pix->height,
> +			 sd_format.format.width, sd_format.format.height);
> +		pix->width = sd_format.format.width;
> +		pix->height = sd_format.format.height;
> +	}
> +
> +	/* Calculate per-plane sizes based on image format */
> +	v4l2_fill_pix_format_mplane(pix, &sd_format.format);
> +	pix->field = V4L2_FIELD_NONE;
> +	ret = ceu_calc_plane_sizes(ceudev, ceu_fmt, pix);
> +	if (ret < 0)
> +		return ret;
> +
> +	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_subdev *ceu_sd = ceudev->sd;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	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_mplane(&format.format, &v4l2_fmt->fmt.pix_mp);
> +	ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, NULL, &format);
> +	if (ret)
> +		return ret;
> +
> +	ceudev->v4l2_pix = v4l2_fmt->fmt.pix_mp;
> +
> +	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_MPLANE,
> +		.fmt.pix_mp = {
> +			.width		= VGA_WIDTH,
> +			.height		= VGA_HEIGHT,
> +			.field		= V4L2_FIELD_NONE,
> +			.pixelformat	= V4L2_PIX_FMT_NV16,
> +			.plane_fmt	= {
> +				[0]	= {
> +					.sizeimage = VGA_WIDTH * VGA_HEIGHT * 2,
> +					.bytesperline = VGA_WIDTH * 2,
> +				},
> +				[1]	= {
> +					.sizeimage = VGA_WIDTH * VGA_HEIGHT * 2,
> +					.bytesperline = VGA_WIDTH * 2,
> +				},
> +			},
> +		},
> +	};
> +
> +	ret = ceu_try_fmt(ceudev, &v4l2_fmt);
> +	if (ret)
> +		return ret;
> +
> +	ceudev->v4l2_pix = v4l2_fmt.fmt.pix_mp;
> +
> +	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;
> + * - YUYV422 if sensor provides YUYV422
> + *
> + * TODO: Other YUYV422 permutations throug data fetch sync mode and DTARY
> + * TODO: Binary data (eg. JPEG) and raw formats through data fetch sync mode
> + */
> +static int ceu_init_formats(struct ceu_device *ceudev)
> +{
> +	struct ceu_subdev *ceu_sd = ceudev->sd;
> +	struct ceu_mbus_fmt *mbus_fmt = &ceu_sd->mbus_fmt;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	bool yuyv_bus_fmt = false;
> +
> +	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 YUYV422 */
> +	while (!yuyv_bus_fmt &&
> +	       !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:
> +		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;
> +		}
> +
> +		sd_mbus_fmt.index++;
> +	}
> +
> +	if (!yuyv_bus_fmt)
> +		return -ENXIO;
> +
> +	/*
> +	 * Save the first encountered YUYV format as "mbus_fmt" and use it
> +	 * to output all planar YUV422 and YUV420 (NV*) formats to memory as
> +	 * well as for data synch fetch mode (YUYV - YVYU etc. ).
> +	 */
> +	mbus_fmt->mbus_code	= sd_mbus_fmt.code;
> +	mbus_fmt->bps		= 8;
> +
> +	/* Annotate the selected bus format components ordering */
> +	switch (sd_mbus_fmt.code) {
> +	case MEDIA_BUS_FMT_YUYV8_2X8:
> +		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_YUYV;
> +		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_YVYU;
> +		mbus_fmt->swapped		= false;
> +		mbus_fmt->bpp			= 16;
> +		break;
> +
> +	case MEDIA_BUS_FMT_YVYU8_2X8:
> +		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_YVYU;
> +		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_YUYV;
> +		mbus_fmt->swapped		= true;
> +		mbus_fmt->bpp			= 16;
> +		break;
> +
> +	case MEDIA_BUS_FMT_UYVY8_2X8:
> +		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_UYVY;
> +		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_VYUY;
> +		mbus_fmt->swapped		= false;
> +		mbus_fmt->bpp			= 16;
> +		break;
> +
> +	case MEDIA_BUS_FMT_VYUY8_2X8:
> +		mbus_fmt->fmt_order		= CEU_CAMCR_DTARY_8_VYUY;
> +		mbus_fmt->fmt_order_swap	= CEU_CAMCR_DTARY_8_UYVY;
> +		mbus_fmt->swapped		= true;
> +		mbus_fmt->bpp			= 16;
> +		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 *v4l2_sd = ceudev->sd->v4l2_sd;
> +
> +	v4l2_subdev_call(v4l2_sd, 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 *v4l2_sd = ceudev->sd->v4l2_sd;
> +
> +	v4l2_subdev_call(v4l2_sd, 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_g_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;

Drop this. You can always return the current format. Only *setting* the format
requires this check.

> +
> +	f->type	= V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> +	f->fmt.pix_mp = ceudev->v4l2_pix;
> +
> +	return 0;
> +}
> +
> +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)
> +{
> +	struct ceu_device *ceudev = video_drvdata(file);
> +
> +	*i = ceudev->sd_index;
> +
> +	return 0;
> +}
> +
> +static int ceu_s_input(struct file *file, void *priv, unsigned int i)
> +{
> +	struct ceu_device *ceudev = video_drvdata(file);
> +	struct ceu_subdev *ceu_sd_old;
> +	int ret;
> +
> +	if (i >= ceudev->num_sd)
> +		return -EINVAL;
> +
> +	ceu_sd_old = ceudev->sd;
> +	ceudev->sd = &ceudev->subdevs[i];
> +
> +	/* Make sure we can generate output image formats. */
> +	ret = ceu_init_formats(ceudev);
> +	if (ret) {
> +		ceudev->sd = 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_sd, core, s_power, 0);
> +	v4l2_subdev_call(ceudev->sd->v4l2_sd, core, s_power, 1);
> +
> +	ceudev->sd_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);
> +
> +	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
> +		return -EINVAL;
> +
> +	return v4l2_subdev_call(ceudev->sd->v4l2_sd, 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_MPLANE)
> +		return -EINVAL;
> +
> +	return v4l2_subdev_call(ceudev->sd->v4l2_sd, 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->sd;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	int ret;
> +
> +	struct v4l2_subdev_frame_size_enum fse = {
> +		.code	= ceu_sd->mbus_fmt.mbus_code,
> +		.index	= fsize->index,
> +		.which	= V4L2_SUBDEV_FORMAT_ACTIVE,
> +	};
> +
> +	ret = v4l2_subdev_call(v4l2_sd, 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);
> +	struct ceu_subdev *ceu_sd = ceudev->sd;
> +	struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd;
> +	int ret;
> +
> +	struct v4l2_subdev_frame_interval_enum fie = {
> +		.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(v4l2_sd, 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_mplane	= ceu_enum_fmt_vid_cap,
> +	.vidioc_try_fmt_vid_cap_mplane	= ceu_try_fmt_vid_cap,
> +	.vidioc_s_fmt_vid_cap_mplane	= ceu_s_fmt_vid_cap,
> +	.vidioc_g_fmt_vid_cap_mplane	= ceu_g_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 *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(asd);
> +
> +	if (video_is_registered(&ceudev->vdev)) {
> +		v4l2_err(&ceudev->v4l2_dev,
> +			 "Video device registered before this sub-device.\n");
> +		return -EBUSY;
> +	}
> +
> +	/* Assign subdevices in the order they appear */
> +	ceu_sd->v4l2_sd = v4l2_sd;
> +	ceudev->num_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 */
> +	q->type			= V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> +	q->io_modes		= VB2_MMAP | VB2_USERPTR;

Please add VB2_DMABUF.

> +	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;
> +
> +	/*
> +	 * Make sure at least one sensor is primary and use it to initialize
> +	 * ceu formats
> +	 */
> +	if (!ceudev->sd) {
> +		ceudev->sd = &ceudev->subdevs[0];
> +		ceudev->sd_index = 0;
> +	}
> +
> +	v4l2_sd = ceudev->sd->v4l2_sd;
> +
> +	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	= v4l2_sd->ctrl_handler;
> +	vdev->fops		= &ceu_fops;
> +	vdev->ioctl_ops		= &ceu_ioctl_ops;
> +	vdev->release		= ceu_vdev_release;
> +	vdev->device_caps	= V4L2_CAP_VIDEO_CAPTURE_MPLANE |

Why MPLANE? It doesn't appear to be needed since there are no multiplane
(really: multibuffer) pixelformats defined.

> +				  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;
> +}
> +
> +/**
> + * ceu_parse_init_sd() - Initialize CEU subdevices and async_subdevs in
> + *			 ceu device. Both DT and platform data parsing use
> + *			 this routine.
> + *
> + * @return 0 for success, -ENOMEM for failure.
> + */
> +static int ceu_parse_init_sd(struct ceu_device *ceudev, unsigned int n_sd)
> +{
> +	/* Reserve memory for 'n_sd' ceu_subdev descriptors */
> +	ceudev->subdevs = devm_kcalloc(ceudev->dev, n_sd,
> +				       sizeof(*ceudev->subdevs), GFP_KERNEL);
> +	if (!ceudev->subdevs)
> +		return -ENOMEM;
> +
> +	/*
> +	 * Reserve memory for 'n_sd' pointers to async_subdevices.
> +	 * ceudev->asds members will point to &ceu_subdev.asd
> +	 */
> +	ceudev->asds = devm_kcalloc(ceudev->dev, n_sd,
> +				    sizeof(*ceudev->asds), GFP_KERNEL);
> +	if (!ceudev->asds)
> +		return -ENOMEM;
> +
> +	ceudev->sd = NULL;
> +	ceudev->sd_index = 0;
> +	ceudev->num_sd = 0;
> +
> +	return 0;
> +}
> +
> +/**
> + * ceu_parse_platform_data() - Initialize async_subdevices using platform
> + *			       device provided data.
> + */
> +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;
> +	int ret;
> +
> +	ret = ceu_parse_init_sd(ceudev, info->num_subdevs);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < info->num_subdevs; i++) {
> +		/* Setup the ceu subdevice and the async subdevice */
> +		async_sd = &info->subdevs[i];
> +		ceu_sd = &ceudev->subdevs[i];
> +
> +		memset(&ceu_sd->asd, 0, sizeof(ceu_sd->asd));
> +		INIT_LIST_HEAD(&ceu_sd->asd.list);
> +
> +		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;
> +
> +		ceudev->asds[i] = &ceu_sd->asd;
> +	}
> +
> +	return info->num_subdevs;
> +}
> +
> +/**
> + * ceu_parse_dt() - Initialize async_subdevs parsing device tree graph
> + */
> +static int ceu_parse_dt(struct ceu_device *ceudev)
> +{
> +	struct device_node *of = ceudev->dev->of_node;
> +	struct v4l2_fwnode_endpoint fw_ep;
> +	struct ceu_subdev *ceu_sd;
> +	struct device_node *ep;
> +	unsigned int i;
> +	int num_ep;
> +	int ret;
> +
> +	num_ep = of_graph_get_endpoint_count(of);
> +	if (num_ep <= 0)
> +		return 0;
> +
> +	ret = ceu_parse_init_sd(ceudev, num_ep);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < num_ep; i++) {
> +		ep = of_graph_get_endpoint_by_regs(of, 0, i);
> +		if (!ep) {
> +			v4l2_err(&ceudev->v4l2_dev,
> +				 "No subdevice connected on port %u.\n", i);
> +			ret = -ENODEV;
> +			goto error_put_node;
> +		}
> +
> +		ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &fw_ep);
> +		if (ret) {
> +			v4l2_err(&ceudev->v4l2_dev,
> +				 "Unable to parse endpoint #%u.\n", i);
> +			goto error_put_node;
> +		}
> +
> +		if (fw_ep.bus_type != V4L2_MBUS_PARALLEL) {
> +			v4l2_err(&ceudev->v4l2_dev,
> +				 "Only parallel input supported.\n");
> +			ret = -EINVAL;
> +			goto error_put_node;
> +		}
> +
> +		/* Setup the ceu subdevice and the async subdevice */
> +		ceu_sd = &ceudev->subdevs[i];
> +		memset(&ceu_sd->asd, 0, sizeof(ceu_sd->asd));
> +		INIT_LIST_HEAD(&ceu_sd->asd.list);
> +
> +		ceu_sd->mbus_flags = fw_ep.bus.parallel.flags;
> +		ceu_sd->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
> +		ceu_sd->asd.match.fwnode.fwnode =
> +			fwnode_graph_get_remote_port_parent(
> +					of_fwnode_handle(ep));
> +
> +		ceudev->asds[i] = &ceu_sd->asd;
> +		of_node_put(ep);
> +	}
> +
> +	return num_ep;
> +
> +error_put_node:
> +	of_node_put(ep);
> +	return ret;
> +}
> +
> +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 num_sd;
> +	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);
> +	if (IS_ERR(res))
> +		return PTR_ERR(res);
> +
> +	base = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(base))
> +		return PTR_ERR(base);
> +	ceudev->base = base;
> +
> +	ret = platform_get_irq(pdev, 0);
> +	if (ret < 0) {
> +		dev_err(dev, "failed to get irq: %d\n", ret);
> +		return ret;
> +	}
> +	irq = ret;
> +
> +	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;
> +	}
> +
> +	pm_suspend_ignore_children(dev, true);
> +	pm_runtime_enable(dev);
> +
> +	ret = v4l2_device_register(dev, &ceudev->v4l2_dev);
> +	if (ret)
> +		goto error_pm_disable;
> +
> +	if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
> +		num_sd = ceu_parse_dt(ceudev);
> +	} else if (dev->platform_data) {
> +		num_sd = ceu_parse_platform_data(ceudev, dev->platform_data);
> +	} else {
> +		dev_err(dev, "CEU platform data not set and no OF support\n");
> +		ret = -EINVAL;
> +		goto error_v4l2_unregister;
> +	}
> +
> +	if (num_sd < 0) {
> +		ret = num_sd;
> +		goto error_v4l2_unregister;
> +	} else if (num_sd == 0)
> +		return 0;
> +
> +	ceudev->notifier.v4l2_dev	= &ceudev->v4l2_dev;
> +	ceudev->notifier.subdevs	= ceudev->asds;
> +	ceudev->notifier.num_subdevs	= num_sd;
> +	ceudev->notifier.bound		= ceu_sensor_bound;
> +	ceudev->notifier.complete	= ceu_sensor_complete;
> +	ret = v4l2_async_notifier_register(&ceudev->v4l2_dev,
> +					   &ceudev->notifier);
> +	if (ret)
> +		goto error_v4l2_unregister_notifier;
> +
> +	dev_info(dev, "Renesas Capture Engine Unit\n");
> +
> +	return 0;
> +
> +error_v4l2_unregister_notifier:
> +	v4l2_async_notifier_unregister(&ceudev->notifier);
> +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[] = {
> +	{ .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
> 

Regards,

	Hans

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

* Re: [PATCH v1 07/10] v4l: i2c: Copy ov772x soc_camera sensor driver
  2017-11-15 10:56 ` [PATCH v1 07/10] v4l: i2c: Copy ov772x soc_camera sensor driver Jacopo Mondi
  2017-12-11 14:49   ` Laurent Pinchart
@ 2017-12-13 12:10   ` Hans Verkuil
  2017-12-13 13:02   ` Philippe Ombredanne
  2 siblings, 0 replies; 56+ messages in thread
From: Hans Verkuil @ 2017-12-13 12:10 UTC (permalink / raw)
  To: Jacopo Mondi, laurent.pinchart, magnus.damm, geert, mchehab
  Cc: linux-renesas-soc, linux-media, linux-sh, linux-kernel

On 15/11/17 11:56, Jacopo Mondi wrote:
> Copy the soc_camera based driver in v4l2 sensor driver directory.
> This commit just copies the original file without modifying it.
> 
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  drivers/media/i2c/ov772x.c | 1124 ++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 1124 insertions(+)
>  create mode 100644 drivers/media/i2c/ov772x.c
> 
> diff --git a/drivers/media/i2c/ov772x.c b/drivers/media/i2c/ov772x.c
> new file mode 100644
> index 0000000..8063835
> --- /dev/null
> +++ b/drivers/media/i2c/ov772x.c
> @@ -0,0 +1,1124 @@
> +/*
> + * ov772x Camera Driver
> + *
> + * Copyright (C) 2008 Renesas Solutions Corp.
> + * Kuninori Morimoto <morimoto.kuninori@renesas.com>
> + *
> + * Based on ov7670 and soc_camera_platform driver,
> + *
> + * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net>
> + * Copyright (C) 2008 Magnus Damm
> + * 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 version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/i2c.h>
> +#include <linux/slab.h>
> +#include <linux/delay.h>
> +#include <linux/v4l2-mediabus.h>
> +#include <linux/videodev2.h>
> +
> +#include <media/i2c/ov772x.h>
> +#include <media/soc_camera.h>
> +#include <media/v4l2-clk.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-subdev.h>
> +#include <media/v4l2-image-sizes.h>
> +
> +/*
> + * register offset
> + */
> +#define GAIN        0x00 /* AGC - Gain control gain setting */
> +#define BLUE        0x01 /* AWB - Blue channel gain setting */
> +#define RED         0x02 /* AWB - Red   channel gain setting */
> +#define GREEN       0x03 /* AWB - Green channel gain setting */
> +#define COM1        0x04 /* Common control 1 */
> +#define BAVG        0x05 /* U/B Average Level */
> +#define GAVG        0x06 /* Y/Gb Average Level */
> +#define RAVG        0x07 /* V/R Average Level */
> +#define AECH        0x08 /* Exposure Value - AEC MSBs */
> +#define COM2        0x09 /* Common control 2 */
> +#define PID         0x0A /* Product ID Number MSB */
> +#define VER         0x0B /* Product ID Number LSB */
> +#define COM3        0x0C /* Common control 3 */
> +#define COM4        0x0D /* Common control 4 */
> +#define COM5        0x0E /* Common control 5 */
> +#define COM6        0x0F /* Common control 6 */
> +#define AEC         0x10 /* Exposure Value */
> +#define CLKRC       0x11 /* Internal clock */
> +#define COM7        0x12 /* Common control 7 */
> +#define COM8        0x13 /* Common control 8 */
> +#define COM9        0x14 /* Common control 9 */
> +#define COM10       0x15 /* Common control 10 */
> +#define REG16       0x16 /* Register 16 */
> +#define HSTART      0x17 /* Horizontal sensor size */
> +#define HSIZE       0x18 /* Horizontal frame (HREF column) end high 8-bit */
> +#define VSTART      0x19 /* Vertical frame (row) start high 8-bit */
> +#define VSIZE       0x1A /* Vertical sensor size */
> +#define PSHFT       0x1B /* Data format - pixel delay select */
> +#define MIDH        0x1C /* Manufacturer ID byte - high */
> +#define MIDL        0x1D /* Manufacturer ID byte - low  */
> +#define LAEC        0x1F /* Fine AEC value */
> +#define COM11       0x20 /* Common control 11 */
> +#define BDBASE      0x22 /* Banding filter Minimum AEC value */
> +#define DBSTEP      0x23 /* Banding filter Maximum Setp */
> +#define AEW         0x24 /* AGC/AEC - Stable operating region (upper limit) */
> +#define AEB         0x25 /* AGC/AEC - Stable operating region (lower limit) */
> +#define VPT         0x26 /* AGC/AEC Fast mode operating region */
> +#define REG28       0x28 /* Register 28 */
> +#define HOUTSIZE    0x29 /* Horizontal data output size MSBs */
> +#define EXHCH       0x2A /* Dummy pixel insert MSB */
> +#define EXHCL       0x2B /* Dummy pixel insert LSB */
> +#define VOUTSIZE    0x2C /* Vertical data output size MSBs */
> +#define ADVFL       0x2D /* LSB of insert dummy lines in Vertical direction */
> +#define ADVFH       0x2E /* MSG of insert dummy lines in Vertical direction */
> +#define YAVE        0x2F /* Y/G Channel Average value */
> +#define LUMHTH      0x30 /* Histogram AEC/AGC Luminance high level threshold */
> +#define LUMLTH      0x31 /* Histogram AEC/AGC Luminance low  level threshold */
> +#define HREF        0x32 /* Image start and size control */
> +#define DM_LNL      0x33 /* Dummy line low  8 bits */
> +#define DM_LNH      0x34 /* Dummy line high 8 bits */
> +#define ADOFF_B     0x35 /* AD offset compensation value for B  channel */
> +#define ADOFF_R     0x36 /* AD offset compensation value for R  channel */
> +#define ADOFF_GB    0x37 /* AD offset compensation value for Gb channel */
> +#define ADOFF_GR    0x38 /* AD offset compensation value for Gr channel */
> +#define OFF_B       0x39 /* Analog process B  channel offset value */
> +#define OFF_R       0x3A /* Analog process R  channel offset value */
> +#define OFF_GB      0x3B /* Analog process Gb channel offset value */
> +#define OFF_GR      0x3C /* Analog process Gr channel offset value */
> +#define COM12       0x3D /* Common control 12 */
> +#define COM13       0x3E /* Common control 13 */
> +#define COM14       0x3F /* Common control 14 */
> +#define COM15       0x40 /* Common control 15*/
> +#define COM16       0x41 /* Common control 16 */
> +#define TGT_B       0x42 /* BLC blue channel target value */
> +#define TGT_R       0x43 /* BLC red  channel target value */
> +#define TGT_GB      0x44 /* BLC Gb   channel target value */
> +#define TGT_GR      0x45 /* BLC Gr   channel target value */
> +/* for ov7720 */
> +#define LCC0        0x46 /* Lens correction control 0 */
> +#define LCC1        0x47 /* Lens correction option 1 - X coordinate */
> +#define LCC2        0x48 /* Lens correction option 2 - Y coordinate */
> +#define LCC3        0x49 /* Lens correction option 3 */
> +#define LCC4        0x4A /* Lens correction option 4 - radius of the circular */
> +#define LCC5        0x4B /* Lens correction option 5 */
> +#define LCC6        0x4C /* Lens correction option 6 */
> +/* for ov7725 */
> +#define LC_CTR      0x46 /* Lens correction control */
> +#define LC_XC       0x47 /* X coordinate of lens correction center relative */
> +#define LC_YC       0x48 /* Y coordinate of lens correction center relative */
> +#define LC_COEF     0x49 /* Lens correction coefficient */
> +#define LC_RADI     0x4A /* Lens correction radius */
> +#define LC_COEFB    0x4B /* Lens B channel compensation coefficient */
> +#define LC_COEFR    0x4C /* Lens R channel compensation coefficient */
> +
> +#define FIXGAIN     0x4D /* Analog fix gain amplifer */
> +#define AREF0       0x4E /* Sensor reference control */
> +#define AREF1       0x4F /* Sensor reference current control */
> +#define AREF2       0x50 /* Analog reference control */
> +#define AREF3       0x51 /* ADC    reference control */
> +#define AREF4       0x52 /* ADC    reference control */
> +#define AREF5       0x53 /* ADC    reference control */
> +#define AREF6       0x54 /* Analog reference control */
> +#define AREF7       0x55 /* Analog reference control */
> +#define UFIX        0x60 /* U channel fixed value output */
> +#define VFIX        0x61 /* V channel fixed value output */
> +#define AWBB_BLK    0x62 /* AWB option for advanced AWB */
> +#define AWB_CTRL0   0x63 /* AWB control byte 0 */
> +#define DSP_CTRL1   0x64 /* DSP control byte 1 */
> +#define DSP_CTRL2   0x65 /* DSP control byte 2 */
> +#define DSP_CTRL3   0x66 /* DSP control byte 3 */
> +#define DSP_CTRL4   0x67 /* DSP control byte 4 */
> +#define AWB_BIAS    0x68 /* AWB BLC level clip */
> +#define AWB_CTRL1   0x69 /* AWB control  1 */
> +#define AWB_CTRL2   0x6A /* AWB control  2 */
> +#define AWB_CTRL3   0x6B /* AWB control  3 */
> +#define AWB_CTRL4   0x6C /* AWB control  4 */
> +#define AWB_CTRL5   0x6D /* AWB control  5 */
> +#define AWB_CTRL6   0x6E /* AWB control  6 */
> +#define AWB_CTRL7   0x6F /* AWB control  7 */
> +#define AWB_CTRL8   0x70 /* AWB control  8 */
> +#define AWB_CTRL9   0x71 /* AWB control  9 */
> +#define AWB_CTRL10  0x72 /* AWB control 10 */
> +#define AWB_CTRL11  0x73 /* AWB control 11 */
> +#define AWB_CTRL12  0x74 /* AWB control 12 */
> +#define AWB_CTRL13  0x75 /* AWB control 13 */
> +#define AWB_CTRL14  0x76 /* AWB control 14 */
> +#define AWB_CTRL15  0x77 /* AWB control 15 */
> +#define AWB_CTRL16  0x78 /* AWB control 16 */
> +#define AWB_CTRL17  0x79 /* AWB control 17 */
> +#define AWB_CTRL18  0x7A /* AWB control 18 */
> +#define AWB_CTRL19  0x7B /* AWB control 19 */
> +#define AWB_CTRL20  0x7C /* AWB control 20 */
> +#define AWB_CTRL21  0x7D /* AWB control 21 */
> +#define GAM1        0x7E /* Gamma Curve  1st segment input end point */
> +#define GAM2        0x7F /* Gamma Curve  2nd segment input end point */
> +#define GAM3        0x80 /* Gamma Curve  3rd segment input end point */
> +#define GAM4        0x81 /* Gamma Curve  4th segment input end point */
> +#define GAM5        0x82 /* Gamma Curve  5th segment input end point */
> +#define GAM6        0x83 /* Gamma Curve  6th segment input end point */
> +#define GAM7        0x84 /* Gamma Curve  7th segment input end point */
> +#define GAM8        0x85 /* Gamma Curve  8th segment input end point */
> +#define GAM9        0x86 /* Gamma Curve  9th segment input end point */
> +#define GAM10       0x87 /* Gamma Curve 10th segment input end point */
> +#define GAM11       0x88 /* Gamma Curve 11th segment input end point */
> +#define GAM12       0x89 /* Gamma Curve 12th segment input end point */
> +#define GAM13       0x8A /* Gamma Curve 13th segment input end point */
> +#define GAM14       0x8B /* Gamma Curve 14th segment input end point */
> +#define GAM15       0x8C /* Gamma Curve 15th segment input end point */
> +#define SLOP        0x8D /* Gamma curve highest segment slope */
> +#define DNSTH       0x8E /* De-noise threshold */
> +#define EDGE_STRNGT 0x8F /* Edge strength  control when manual mode */
> +#define EDGE_TRSHLD 0x90 /* Edge threshold control when manual mode */
> +#define DNSOFF      0x91 /* Auto De-noise threshold control */
> +#define EDGE_UPPER  0x92 /* Edge strength upper limit when Auto mode */
> +#define EDGE_LOWER  0x93 /* Edge strength lower limit when Auto mode */
> +#define MTX1        0x94 /* Matrix coefficient 1 */
> +#define MTX2        0x95 /* Matrix coefficient 2 */
> +#define MTX3        0x96 /* Matrix coefficient 3 */
> +#define MTX4        0x97 /* Matrix coefficient 4 */
> +#define MTX5        0x98 /* Matrix coefficient 5 */
> +#define MTX6        0x99 /* Matrix coefficient 6 */
> +#define MTX_CTRL    0x9A /* Matrix control */
> +#define BRIGHT      0x9B /* Brightness control */
> +#define CNTRST      0x9C /* Contrast contrast */
> +#define CNTRST_CTRL 0x9D /* Contrast contrast center */
> +#define UVAD_J0     0x9E /* Auto UV adjust contrast 0 */
> +#define UVAD_J1     0x9F /* Auto UV adjust contrast 1 */
> +#define SCAL0       0xA0 /* Scaling control 0 */
> +#define SCAL1       0xA1 /* Scaling control 1 */
> +#define SCAL2       0xA2 /* Scaling control 2 */
> +#define FIFODLYM    0xA3 /* FIFO manual mode delay control */
> +#define FIFODLYA    0xA4 /* FIFO auto   mode delay control */
> +#define SDE         0xA6 /* Special digital effect control */
> +#define USAT        0xA7 /* U component saturation control */
> +#define VSAT        0xA8 /* V component saturation control */
> +/* for ov7720 */
> +#define HUE0        0xA9 /* Hue control 0 */
> +#define HUE1        0xAA /* Hue control 1 */
> +/* for ov7725 */
> +#define HUECOS      0xA9 /* Cosine value */
> +#define HUESIN      0xAA /* Sine value */
> +
> +#define SIGN        0xAB /* Sign bit for Hue and contrast */
> +#define DSPAUTO     0xAC /* DSP auto function ON/OFF control */
> +
> +/*
> + * register detail
> + */
> +
> +/* COM2 */
> +#define SOFT_SLEEP_MODE 0x10	/* Soft sleep mode */
> +				/* Output drive capability */
> +#define OCAP_1x         0x00	/* 1x */
> +#define OCAP_2x         0x01	/* 2x */
> +#define OCAP_3x         0x02	/* 3x */
> +#define OCAP_4x         0x03	/* 4x */
> +
> +/* COM3 */
> +#define SWAP_MASK       (SWAP_RGB | SWAP_YUV | SWAP_ML)
> +#define IMG_MASK        (VFLIP_IMG | HFLIP_IMG)
> +
> +#define VFLIP_IMG       0x80	/* Vertical flip image ON/OFF selection */
> +#define HFLIP_IMG       0x40	/* Horizontal mirror image ON/OFF selection */
> +#define SWAP_RGB        0x20	/* Swap B/R  output sequence in RGB mode */
> +#define SWAP_YUV        0x10	/* Swap Y/UV output sequence in YUV mode */
> +#define SWAP_ML         0x08	/* Swap output MSB/LSB */
> +				/* Tri-state option for output clock */
> +#define NOTRI_CLOCK     0x04	/*   0: Tri-state    at this period */
> +				/*   1: No tri-state at this period */
> +				/* Tri-state option for output data */
> +#define NOTRI_DATA      0x02	/*   0: Tri-state    at this period */
> +				/*   1: No tri-state at this period */
> +#define SCOLOR_TEST     0x01	/* Sensor color bar test pattern */
> +
> +/* COM4 */
> +				/* PLL frequency control */
> +#define PLL_BYPASS      0x00	/*  00: Bypass PLL */
> +#define PLL_4x          0x40	/*  01: PLL 4x */
> +#define PLL_6x          0x80	/*  10: PLL 6x */
> +#define PLL_8x          0xc0	/*  11: PLL 8x */
> +				/* AEC evaluate window */
> +#define AEC_FULL        0x00	/*  00: Full window */
> +#define AEC_1p2         0x10	/*  01: 1/2  window */
> +#define AEC_1p4         0x20	/*  10: 1/4  window */
> +#define AEC_2p3         0x30	/*  11: Low 2/3 window */
> +
> +/* COM5 */
> +#define AFR_ON_OFF      0x80	/* Auto frame rate control ON/OFF selection */
> +#define AFR_SPPED       0x40	/* Auto frame rate control speed selection */
> +				/* Auto frame rate max rate control */
> +#define AFR_NO_RATE     0x00	/*     No  reduction of frame rate */
> +#define AFR_1p2         0x10	/*     Max reduction to 1/2 frame rate */
> +#define AFR_1p4         0x20	/*     Max reduction to 1/4 frame rate */
> +#define AFR_1p8         0x30	/* Max reduction to 1/8 frame rate */
> +				/* Auto frame rate active point control */
> +#define AF_2x           0x00	/*     Add frame when AGC reaches  2x gain */
> +#define AF_4x           0x04	/*     Add frame when AGC reaches  4x gain */
> +#define AF_8x           0x08	/*     Add frame when AGC reaches  8x gain */
> +#define AF_16x          0x0c	/* Add frame when AGC reaches 16x gain */
> +				/* AEC max step control */
> +#define AEC_NO_LIMIT    0x01	/*   0 : AEC incease step has limit */
> +				/*   1 : No limit to AEC increase step */
> +
> +/* COM7 */
> +				/* SCCB Register Reset */
> +#define SCCB_RESET      0x80	/*   0 : No change */
> +				/*   1 : Resets all registers to default */
> +				/* Resolution selection */
> +#define SLCT_MASK       0x40	/*   Mask of VGA or QVGA */
> +#define SLCT_VGA        0x00	/*   0 : VGA */
> +#define SLCT_QVGA       0x40	/*   1 : QVGA */
> +#define ITU656_ON_OFF   0x20	/* ITU656 protocol ON/OFF selection */
> +#define SENSOR_RAW	0x10	/* Sensor RAW */
> +				/* RGB output format control */
> +#define FMT_MASK        0x0c	/*      Mask of color format */
> +#define FMT_GBR422      0x00	/*      00 : GBR 4:2:2 */
> +#define FMT_RGB565      0x04	/*      01 : RGB 565 */
> +#define FMT_RGB555      0x08	/*      10 : RGB 555 */
> +#define FMT_RGB444      0x0c	/* 11 : RGB 444 */
> +				/* Output format control */
> +#define OFMT_MASK       0x03    /*      Mask of output format */
> +#define OFMT_YUV        0x00	/*      00 : YUV */
> +#define OFMT_P_BRAW     0x01	/*      01 : Processed Bayer RAW */
> +#define OFMT_RGB        0x02	/*      10 : RGB */
> +#define OFMT_BRAW       0x03	/* 11 : Bayer RAW */
> +
> +/* COM8 */
> +#define FAST_ALGO       0x80	/* Enable fast AGC/AEC algorithm */
> +				/* AEC Setp size limit */
> +#define UNLMT_STEP      0x40	/*   0 : Step size is limited */
> +				/*   1 : Unlimited step size */
> +#define BNDF_ON_OFF     0x20	/* Banding filter ON/OFF */
> +#define AEC_BND         0x10	/* Enable AEC below banding value */
> +#define AEC_ON_OFF      0x08	/* Fine AEC ON/OFF control */
> +#define AGC_ON          0x04	/* AGC Enable */
> +#define AWB_ON          0x02	/* AWB Enable */
> +#define AEC_ON          0x01	/* AEC Enable */
> +
> +/* COM9 */
> +#define BASE_AECAGC     0x80	/* Histogram or average based AEC/AGC */
> +				/* Automatic gain ceiling - maximum AGC value */
> +#define GAIN_2x         0x00	/*    000 :   2x */
> +#define GAIN_4x         0x10	/*    001 :   4x */
> +#define GAIN_8x         0x20	/*    010 :   8x */
> +#define GAIN_16x        0x30	/*    011 :  16x */
> +#define GAIN_32x        0x40	/*    100 :  32x */
> +#define GAIN_64x        0x50	/* 101 :  64x */
> +#define GAIN_128x       0x60	/* 110 : 128x */
> +#define DROP_VSYNC      0x04	/* Drop VSYNC output of corrupt frame */
> +#define DROP_HREF       0x02	/* Drop HREF  output of corrupt frame */
> +
> +/* COM11 */
> +#define SGLF_ON_OFF     0x02	/* Single frame ON/OFF selection */
> +#define SGLF_TRIG       0x01	/* Single frame transfer trigger */
> +
> +/* HREF */
> +#define HREF_VSTART_SHIFT	6	/* VSTART LSB */
> +#define HREF_HSTART_SHIFT	4	/* HSTART 2 LSBs */
> +#define HREF_VSIZE_SHIFT	2	/* VSIZE LSB */
> +#define HREF_HSIZE_SHIFT	0	/* HSIZE 2 LSBs */
> +
> +/* EXHCH */
> +#define EXHCH_VSIZE_SHIFT	2	/* VOUTSIZE LSB */
> +#define EXHCH_HSIZE_SHIFT	0	/* HOUTSIZE 2 LSBs */
> +
> +/* DSP_CTRL1 */
> +#define FIFO_ON         0x80	/* FIFO enable/disable selection */
> +#define UV_ON_OFF       0x40	/* UV adjust function ON/OFF selection */
> +#define YUV444_2_422    0x20	/* YUV444 to 422 UV channel option selection */
> +#define CLR_MTRX_ON_OFF 0x10	/* Color matrix ON/OFF selection */
> +#define INTPLT_ON_OFF   0x08	/* Interpolation ON/OFF selection */
> +#define GMM_ON_OFF      0x04	/* Gamma function ON/OFF selection */
> +#define AUTO_BLK_ON_OFF 0x02	/* Black defect auto correction ON/OFF */
> +#define AUTO_WHT_ON_OFF 0x01	/* White define auto correction ON/OFF */
> +
> +/* DSP_CTRL3 */
> +#define UV_MASK         0x80	/* UV output sequence option */
> +#define UV_ON           0x80	/*   ON */
> +#define UV_OFF          0x00	/*   OFF */
> +#define CBAR_MASK       0x20	/* DSP Color bar mask */
> +#define CBAR_ON         0x20	/*   ON */
> +#define CBAR_OFF        0x00	/*   OFF */
> +
> +/* DSP_CTRL4 */
> +#define DSP_OFMT_YUV	0x00
> +#define DSP_OFMT_RGB	0x00
> +#define DSP_OFMT_RAW8	0x02
> +#define DSP_OFMT_RAW10	0x03
> +
> +/* DSPAUTO (DSP Auto Function ON/OFF Control) */
> +#define AWB_ACTRL       0x80 /* AWB auto threshold control */
> +#define DENOISE_ACTRL   0x40 /* De-noise auto threshold control */
> +#define EDGE_ACTRL      0x20 /* Edge enhancement auto strength control */
> +#define UV_ACTRL        0x10 /* UV adjust auto slope control */
> +#define SCAL0_ACTRL     0x08 /* Auto scaling factor control */
> +#define SCAL1_2_ACTRL   0x04 /* Auto scaling factor control */
> +
> +#define OV772X_MAX_WIDTH	VGA_WIDTH
> +#define OV772X_MAX_HEIGHT	VGA_HEIGHT
> +
> +/*
> + * ID
> + */
> +#define OV7720  0x7720
> +#define OV7725  0x7721
> +#define VERSION(pid, ver) ((pid<<8)|(ver&0xFF))
> +
> +/*
> + * struct
> + */
> +
> +struct ov772x_color_format {
> +	u32 code;
> +	enum v4l2_colorspace colorspace;
> +	u8 dsp3;
> +	u8 dsp4;
> +	u8 com3;
> +	u8 com7;
> +};
> +
> +struct ov772x_win_size {
> +	char                     *name;
> +	unsigned char             com7_bit;
> +	struct v4l2_rect	  rect;
> +};
> +
> +struct ov772x_priv {
> +	struct v4l2_subdev                subdev;
> +	struct v4l2_ctrl_handler	  hdl;
> +	struct v4l2_clk			 *clk;
> +	struct ov772x_camera_info        *info;
> +	const struct ov772x_color_format *cfmt;
> +	const struct ov772x_win_size     *win;
> +	unsigned short                    flag_vflip:1;
> +	unsigned short                    flag_hflip:1;
> +	/* band_filter = COM8[5] ? 256 - BDBASE : 0 */
> +	unsigned short                    band_filter;
> +};
> +
> +/*
> + * supported color format list
> + */
> +static const struct ov772x_color_format ov772x_cfmts[] = {
> +	{
> +		.code		= MEDIA_BUS_FMT_YUYV8_2X8,
> +		.colorspace	= V4L2_COLORSPACE_JPEG,

It's just COLORSPACE_SRGB for all.

> +		.dsp3		= 0x0,
> +		.dsp4		= DSP_OFMT_YUV,
> +		.com3		= SWAP_YUV,
> +		.com7		= OFMT_YUV,
> +	},
> +	{
> +		.code		= MEDIA_BUS_FMT_YVYU8_2X8,
> +		.colorspace	= V4L2_COLORSPACE_JPEG,
> +		.dsp3		= UV_ON,
> +		.dsp4		= DSP_OFMT_YUV,
> +		.com3		= SWAP_YUV,
> +		.com7		= OFMT_YUV,
> +	},
> +	{
> +		.code		= MEDIA_BUS_FMT_UYVY8_2X8,
> +		.colorspace	= V4L2_COLORSPACE_JPEG,
> +		.dsp3		= 0x0,
> +		.dsp4		= DSP_OFMT_YUV,
> +		.com3		= 0x0,
> +		.com7		= OFMT_YUV,
> +	},
> +	{
> +		.code		= MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE,
> +		.colorspace	= V4L2_COLORSPACE_SRGB,
> +		.dsp3		= 0x0,
> +		.dsp4		= DSP_OFMT_YUV,
> +		.com3		= SWAP_RGB,
> +		.com7		= FMT_RGB555 | OFMT_RGB,
> +	},
> +	{
> +		.code		= MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE,
> +		.colorspace	= V4L2_COLORSPACE_SRGB,
> +		.dsp3		= 0x0,
> +		.dsp4		= DSP_OFMT_YUV,
> +		.com3		= 0x0,
> +		.com7		= FMT_RGB555 | OFMT_RGB,
> +	},
> +	{
> +		.code		= MEDIA_BUS_FMT_RGB565_2X8_LE,
> +		.colorspace	= V4L2_COLORSPACE_SRGB,
> +		.dsp3		= 0x0,
> +		.dsp4		= DSP_OFMT_YUV,
> +		.com3		= SWAP_RGB,
> +		.com7		= FMT_RGB565 | OFMT_RGB,
> +	},
> +	{
> +		.code		= MEDIA_BUS_FMT_RGB565_2X8_BE,
> +		.colorspace	= V4L2_COLORSPACE_SRGB,
> +		.dsp3		= 0x0,
> +		.dsp4		= DSP_OFMT_YUV,
> +		.com3		= 0x0,
> +		.com7		= FMT_RGB565 | OFMT_RGB,
> +	},
> +	{
> +		/* Setting DSP4 to DSP_OFMT_RAW8 still gives 10-bit output,
> +		 * regardless of the COM7 value. We can thus only support 10-bit
> +		 * Bayer until someone figures it out.
> +		 */
> +		.code		= MEDIA_BUS_FMT_SBGGR10_1X10,
> +		.colorspace	= V4L2_COLORSPACE_SRGB,
> +		.dsp3		= 0x0,
> +		.dsp4		= DSP_OFMT_RAW10,
> +		.com3		= 0x0,
> +		.com7		= SENSOR_RAW | OFMT_BRAW,
> +	},
> +};
> +
> +
> +/*
> + * window size list
> + */
> +
> +static const struct ov772x_win_size ov772x_win_sizes[] = {
> +	{
> +		.name     = "VGA",
> +		.com7_bit = SLCT_VGA,
> +		.rect = {
> +			.left = 140,
> +			.top = 14,
> +			.width = VGA_WIDTH,
> +			.height = VGA_HEIGHT,
> +		},
> +	}, {
> +		.name     = "QVGA",
> +		.com7_bit = SLCT_QVGA,
> +		.rect = {
> +			.left = 252,
> +			.top = 6,
> +			.width = QVGA_WIDTH,
> +			.height = QVGA_HEIGHT,
> +		},
> +	},
> +};
> +
> +/*
> + * general function
> + */
> +
> +static struct ov772x_priv *to_ov772x(struct v4l2_subdev *sd)
> +{
> +	return container_of(sd, struct ov772x_priv, subdev);
> +}
> +
> +static inline int ov772x_read(struct i2c_client *client, u8 addr)
> +{
> +	return i2c_smbus_read_byte_data(client, addr);
> +}
> +
> +static inline int ov772x_write(struct i2c_client *client, u8 addr, u8 value)
> +{
> +	return i2c_smbus_write_byte_data(client, addr, value);
> +}
> +
> +static int ov772x_mask_set(struct i2c_client *client, u8  command, u8  mask,
> +			   u8  set)
> +{
> +	s32 val = ov772x_read(client, command);
> +	if (val < 0)
> +		return val;
> +
> +	val &= ~mask;
> +	val |= set & mask;
> +
> +	return ov772x_write(client, command, val);
> +}
> +
> +static int ov772x_reset(struct i2c_client *client)
> +{
> +	int ret;
> +
> +	ret = ov772x_write(client, COM7, SCCB_RESET);
> +	if (ret < 0)
> +		return ret;
> +
> +	msleep(1);
> +
> +	return ov772x_mask_set(client, COM2, SOFT_SLEEP_MODE, SOFT_SLEEP_MODE);
> +}
> +
> +/*
> + * soc_camera_ops function
> + */
> +
> +static int ov772x_s_stream(struct v4l2_subdev *sd, int enable)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(sd);
> +	struct ov772x_priv *priv = to_ov772x(sd);
> +
> +	if (!enable) {
> +		ov772x_mask_set(client, COM2, SOFT_SLEEP_MODE, SOFT_SLEEP_MODE);
> +		return 0;
> +	}
> +
> +	ov772x_mask_set(client, COM2, SOFT_SLEEP_MODE, 0);
> +
> +	dev_dbg(&client->dev, "format %d, win %s\n",
> +		priv->cfmt->code, priv->win->name);
> +
> +	return 0;
> +}
> +
> +static int ov772x_s_ctrl(struct v4l2_ctrl *ctrl)
> +{
> +	struct ov772x_priv *priv = container_of(ctrl->handler,
> +						struct ov772x_priv, hdl);
> +	struct v4l2_subdev *sd = &priv->subdev;
> +	struct i2c_client *client = v4l2_get_subdevdata(sd);
> +	int ret = 0;
> +	u8 val;
> +
> +	switch (ctrl->id) {
> +	case V4L2_CID_VFLIP:
> +		val = ctrl->val ? VFLIP_IMG : 0x00;
> +		priv->flag_vflip = ctrl->val;
> +		if (priv->info->flags & OV772X_FLAG_VFLIP)
> +			val ^= VFLIP_IMG;
> +		return ov772x_mask_set(client, COM3, VFLIP_IMG, val);
> +	case V4L2_CID_HFLIP:
> +		val = ctrl->val ? HFLIP_IMG : 0x00;
> +		priv->flag_hflip = ctrl->val;
> +		if (priv->info->flags & OV772X_FLAG_HFLIP)
> +			val ^= HFLIP_IMG;
> +		return ov772x_mask_set(client, COM3, HFLIP_IMG, val);
> +	case V4L2_CID_BAND_STOP_FILTER:
> +		if (!ctrl->val) {
> +			/* Switch the filter off, it is on now */
> +			ret = ov772x_mask_set(client, BDBASE, 0xff, 0xff);
> +			if (!ret)
> +				ret = ov772x_mask_set(client, COM8,
> +						      BNDF_ON_OFF, 0);
> +		} else {
> +			/* Switch the filter on, set AEC low limit */
> +			val = 256 - ctrl->val;
> +			ret = ov772x_mask_set(client, COM8,
> +					      BNDF_ON_OFF, BNDF_ON_OFF);
> +			if (!ret)
> +				ret = ov772x_mask_set(client, BDBASE,
> +						      0xff, val);
> +		}
> +		if (!ret)
> +			priv->band_filter = ctrl->val;
> +		return ret;
> +	}
> +
> +	return -EINVAL;
> +}
> +
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +static int ov772x_g_register(struct v4l2_subdev *sd,
> +			     struct v4l2_dbg_register *reg)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(sd);
> +	int ret;
> +
> +	reg->size = 1;
> +	if (reg->reg > 0xff)
> +		return -EINVAL;
> +
> +	ret = ov772x_read(client, reg->reg);
> +	if (ret < 0)
> +		return ret;
> +
> +	reg->val = (__u64)ret;
> +
> +	return 0;
> +}
> +
> +static int ov772x_s_register(struct v4l2_subdev *sd,
> +			     const struct v4l2_dbg_register *reg)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(sd);
> +
> +	if (reg->reg > 0xff ||
> +	    reg->val > 0xff)
> +		return -EINVAL;
> +
> +	return ov772x_write(client, reg->reg, reg->val);
> +}
> +#endif
> +
> +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);
> +}
> +
> +static const struct ov772x_win_size *ov772x_select_win(u32 width, u32 height)
> +{
> +	const struct ov772x_win_size *win = &ov772x_win_sizes[0];
> +	u32 best_diff = UINT_MAX;
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(ov772x_win_sizes); ++i) {
> +		u32 diff = abs(width - ov772x_win_sizes[i].rect.width)
> +			 + abs(height - ov772x_win_sizes[i].rect.height);
> +		if (diff < best_diff) {
> +			best_diff = diff;
> +			win = &ov772x_win_sizes[i];
> +		}
> +	}
> +
> +	return win;
> +}
> +
> +static void ov772x_select_params(const struct v4l2_mbus_framefmt *mf,
> +				 const struct ov772x_color_format **cfmt,
> +				 const struct ov772x_win_size **win)
> +{
> +	unsigned int i;
> +
> +	/* Select a format. */
> +	*cfmt = &ov772x_cfmts[0];
> +
> +	for (i = 0; i < ARRAY_SIZE(ov772x_cfmts); i++) {
> +		if (mf->code == ov772x_cfmts[i].code) {
> +			*cfmt = &ov772x_cfmts[i];
> +			break;
> +		}
> +	}
> +
> +	/* Select a window size. */
> +	*win = ov772x_select_win(mf->width, mf->height);
> +}
> +
> +static int ov772x_set_params(struct ov772x_priv *priv,
> +			     const struct ov772x_color_format *cfmt,
> +			     const struct ov772x_win_size *win)
> +{
> +	struct i2c_client *client = v4l2_get_subdevdata(&priv->subdev);
> +	int ret;
> +	u8  val;
> +
> +	/*
> +	 * reset hardware
> +	 */
> +	ov772x_reset(client);
> +
> +	/*
> +	 * Edge Ctrl
> +	 */
> +	if (priv->info->edgectrl.strength & OV772X_MANUAL_EDGE_CTRL) {
> +
> +		/*
> +		 * Manual Edge Control Mode
> +		 *
> +		 * Edge auto strength bit is set by default.
> +		 * Remove it when manual mode.
> +		 */
> +
> +		ret = ov772x_mask_set(client, DSPAUTO, EDGE_ACTRL, 0x00);
> +		if (ret < 0)
> +			goto ov772x_set_fmt_error;
> +
> +		ret = ov772x_mask_set(client,
> +				      EDGE_TRSHLD, OV772X_EDGE_THRESHOLD_MASK,
> +				      priv->info->edgectrl.threshold);
> +		if (ret < 0)
> +			goto ov772x_set_fmt_error;
> +
> +		ret = ov772x_mask_set(client,
> +				      EDGE_STRNGT, OV772X_EDGE_STRENGTH_MASK,
> +				      priv->info->edgectrl.strength);
> +		if (ret < 0)
> +			goto ov772x_set_fmt_error;
> +
> +	} else if (priv->info->edgectrl.upper > priv->info->edgectrl.lower) {
> +		/*
> +		 * Auto Edge Control Mode
> +		 *
> +		 * set upper and lower limit
> +		 */
> +		ret = ov772x_mask_set(client,
> +				      EDGE_UPPER, OV772X_EDGE_UPPER_MASK,
> +				      priv->info->edgectrl.upper);
> +		if (ret < 0)
> +			goto ov772x_set_fmt_error;
> +
> +		ret = ov772x_mask_set(client,
> +				      EDGE_LOWER, OV772X_EDGE_LOWER_MASK,
> +				      priv->info->edgectrl.lower);
> +		if (ret < 0)
> +			goto ov772x_set_fmt_error;
> +	}
> +
> +	/* Format and window size */
> +	ret = ov772x_write(client, HSTART, win->rect.left >> 2);
> +	if (ret < 0)
> +		goto ov772x_set_fmt_error;
> +	ret = ov772x_write(client, HSIZE, win->rect.width >> 2);
> +	if (ret < 0)
> +		goto ov772x_set_fmt_error;
> +	ret = ov772x_write(client, VSTART, win->rect.top >> 1);
> +	if (ret < 0)
> +		goto ov772x_set_fmt_error;
> +	ret = ov772x_write(client, VSIZE, win->rect.height >> 1);
> +	if (ret < 0)
> +		goto ov772x_set_fmt_error;
> +	ret = ov772x_write(client, HOUTSIZE, win->rect.width >> 2);
> +	if (ret < 0)
> +		goto ov772x_set_fmt_error;
> +	ret = ov772x_write(client, VOUTSIZE, win->rect.height >> 1);
> +	if (ret < 0)
> +		goto ov772x_set_fmt_error;
> +	ret = ov772x_write(client, HREF,
> +			   ((win->rect.top & 1) << HREF_VSTART_SHIFT) |
> +			   ((win->rect.left & 3) << HREF_HSTART_SHIFT) |
> +			   ((win->rect.height & 1) << HREF_VSIZE_SHIFT) |
> +			   ((win->rect.width & 3) << HREF_HSIZE_SHIFT));
> +	if (ret < 0)
> +		goto ov772x_set_fmt_error;
> +	ret = ov772x_write(client, EXHCH,
> +			   ((win->rect.height & 1) << EXHCH_VSIZE_SHIFT) |
> +			   ((win->rect.width & 3) << EXHCH_HSIZE_SHIFT));
> +	if (ret < 0)
> +		goto ov772x_set_fmt_error;
> +
> +	/*
> +	 * set DSP_CTRL3
> +	 */
> +	val = cfmt->dsp3;
> +	if (val) {
> +		ret = ov772x_mask_set(client,
> +				      DSP_CTRL3, UV_MASK, val);
> +		if (ret < 0)
> +			goto ov772x_set_fmt_error;
> +	}
> +
> +	/* DSP_CTRL4: AEC reference point and DSP output format. */
> +	if (cfmt->dsp4) {
> +		ret = ov772x_write(client, DSP_CTRL4, cfmt->dsp4);
> +		if (ret < 0)
> +			goto ov772x_set_fmt_error;
> +	}
> +
> +	/*
> +	 * set COM3
> +	 */
> +	val = cfmt->com3;
> +	if (priv->info->flags & OV772X_FLAG_VFLIP)
> +		val |= VFLIP_IMG;
> +	if (priv->info->flags & OV772X_FLAG_HFLIP)
> +		val |= HFLIP_IMG;
> +	if (priv->flag_vflip)
> +		val ^= VFLIP_IMG;
> +	if (priv->flag_hflip)
> +		val ^= HFLIP_IMG;
> +
> +	ret = ov772x_mask_set(client,
> +			      COM3, SWAP_MASK | IMG_MASK, val);
> +	if (ret < 0)
> +		goto ov772x_set_fmt_error;
> +
> +	/* COM7: Sensor resolution and output format control. */
> +	ret = ov772x_write(client, COM7, win->com7_bit | cfmt->com7);
> +	if (ret < 0)
> +		goto ov772x_set_fmt_error;
> +
> +	/*
> +	 * set COM8
> +	 */
> +	if (priv->band_filter) {
> +		ret = ov772x_mask_set(client, COM8, BNDF_ON_OFF, 1);
> +		if (!ret)
> +			ret = ov772x_mask_set(client, BDBASE,
> +					      0xff, 256 - priv->band_filter);
> +		if (ret < 0)
> +			goto ov772x_set_fmt_error;
> +	}
> +
> +	return ret;
> +
> +ov772x_set_fmt_error:
> +
> +	ov772x_reset(client);
> +
> +	return ret;
> +}
> +
> +static int ov772x_get_selection(struct v4l2_subdev *sd,
> +		struct v4l2_subdev_pad_config *cfg,
> +		struct v4l2_subdev_selection *sel)
> +{
> +	if (sel->which != V4L2_SUBDEV_FORMAT_ACTIVE)
> +		return -EINVAL;
> +
> +	sel->r.left = 0;
> +	sel->r.top = 0;
> +	switch (sel->target) {
> +	case V4L2_SEL_TGT_CROP_BOUNDS:
> +	case V4L2_SEL_TGT_CROP_DEFAULT:
> +		sel->r.width = OV772X_MAX_WIDTH;
> +		sel->r.height = OV772X_MAX_HEIGHT;
> +		return 0;
> +	case V4L2_SEL_TGT_CROP:
> +		sel->r.width = VGA_WIDTH;
> +		sel->r.height = VGA_HEIGHT;

If you don't do actual cropping, then all three should return the current
selected framesize (VGA or QVGA).

> +		return 0;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int ov772x_get_fmt(struct v4l2_subdev *sd,
> +		struct v4l2_subdev_pad_config *cfg,
> +		struct v4l2_subdev_format *format)
> +{
> +	struct v4l2_mbus_framefmt *mf = &format->format;
> +	struct ov772x_priv *priv = to_ov772x(sd);
> +
> +	if (format->pad)
> +		return -EINVAL;
> +
> +	mf->width	= priv->win->rect.width;
> +	mf->height	= priv->win->rect.height;
> +	mf->code	= priv->cfmt->code;
> +	mf->colorspace	= priv->cfmt->colorspace;
> +	mf->field	= V4L2_FIELD_NONE;
> +
> +	return 0;
> +}
> +
> +static int ov772x_set_fmt(struct v4l2_subdev *sd,
> +		struct v4l2_subdev_pad_config *cfg,
> +		struct v4l2_subdev_format *format)
> +{
> +	struct ov772x_priv *priv = to_ov772x(sd);
> +	struct v4l2_mbus_framefmt *mf = &format->format;
> +	const struct ov772x_color_format *cfmt;
> +	const struct ov772x_win_size *win;
> +	int ret;
> +
> +	if (format->pad)
> +		return -EINVAL;
> +
> +	ov772x_select_params(mf, &cfmt, &win);
> +
> +	mf->code = cfmt->code;
> +	mf->width = win->rect.width;
> +	mf->height = win->rect.height;
> +	mf->field = V4L2_FIELD_NONE;
> +	mf->colorspace = cfmt->colorspace;
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_TRY) {
> +		cfg->try_fmt = *mf;
> +		return 0;
> +	}
> +
> +	ret = ov772x_set_params(priv, cfmt, win);
> +	if (ret < 0)
> +		return ret;
> +
> +	priv->win = win;
> +	priv->cfmt = cfmt;
> +	return 0;
> +}
> +
> +static int ov772x_video_probe(struct ov772x_priv *priv)
> +{
> +	struct i2c_client  *client = v4l2_get_subdevdata(&priv->subdev);
> +	u8                  pid, ver;
> +	const char         *devname;
> +	int		    ret;
> +
> +	ret = ov772x_s_power(&priv->subdev, 1);
> +	if (ret < 0)
> +		return ret;
> +
> +	/*
> +	 * check and show product ID and manufacturer ID
> +	 */
> +	pid = ov772x_read(client, PID);
> +	ver = ov772x_read(client, VER);
> +
> +	switch (VERSION(pid, ver)) {
> +	case OV7720:
> +		devname     = "ov7720";
> +		break;
> +	case OV7725:
> +		devname     = "ov7725";
> +		break;
> +	default:
> +		dev_err(&client->dev,
> +			"Product ID error %x:%x\n", pid, ver);
> +		ret = -ENODEV;
> +		goto done;
> +	}
> +
> +	dev_info(&client->dev,
> +		 "%s Product ID %0x:%0x Manufacturer ID %x:%x\n",
> +		 devname,
> +		 pid,
> +		 ver,
> +		 ov772x_read(client, MIDH),
> +		 ov772x_read(client, MIDL));
> +	ret = v4l2_ctrl_handler_setup(&priv->hdl);
> +
> +done:
> +	ov772x_s_power(&priv->subdev, 0);
> +	return ret;
> +}
> +
> +static const struct v4l2_ctrl_ops ov772x_ctrl_ops = {
> +	.s_ctrl = ov772x_s_ctrl,
> +};
> +
> +static const struct v4l2_subdev_core_ops ov772x_subdev_core_ops = {
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> +	.g_register	= ov772x_g_register,
> +	.s_register	= ov772x_s_register,
> +#endif
> +	.s_power	= ov772x_s_power,
> +};
> +
> +static int ov772x_enum_mbus_code(struct v4l2_subdev *sd,
> +		struct v4l2_subdev_pad_config *cfg,
> +		struct v4l2_subdev_mbus_code_enum *code)
> +{
> +	if (code->pad || code->index >= ARRAY_SIZE(ov772x_cfmts))
> +		return -EINVAL;
> +
> +	code->code = ov772x_cfmts[code->index].code;
> +	return 0;
> +}
> +
> +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;
> +}

Drop this, this should be specified in the DT. See also my comment in patch 3/10.
This op dates from pre-devicetree times and is really no longer needed.

> +
> +static const struct v4l2_subdev_video_ops ov772x_subdev_video_ops = {
> +	.s_stream	= ov772x_s_stream,
> +	.g_mbus_config	= ov772x_g_mbus_config,
> +};
> +
> +static const struct v4l2_subdev_pad_ops ov772x_subdev_pad_ops = {
> +	.enum_mbus_code = ov772x_enum_mbus_code,
> +	.get_selection	= ov772x_get_selection,
> +	.get_fmt	= ov772x_get_fmt,
> +	.set_fmt	= ov772x_set_fmt,
> +};
> +
> +static const struct v4l2_subdev_ops ov772x_subdev_ops = {
> +	.core	= &ov772x_subdev_core_ops,
> +	.video	= &ov772x_subdev_video_ops,
> +	.pad	= &ov772x_subdev_pad_ops,
> +};
> +
> +/*
> + * i2c_driver function
> + */
> +
> +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);
> +	int			ret;
> +
> +	if (!ssdd || !ssdd->drv_priv) {
> +		dev_err(&client->dev, "OV772X: missing platform data!\n");
> +		return -EINVAL;
> +	}
> +
> +	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |
> +					      I2C_FUNC_PROTOCOL_MANGLING)) {
> +		dev_err(&adapter->dev,
> +			"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)
> +		return -ENOMEM;
> +
> +	priv->info = ssdd->drv_priv;
> +
> +	v4l2_i2c_subdev_init(&priv->subdev, client, &ov772x_subdev_ops);
> +	v4l2_ctrl_handler_init(&priv->hdl, 3);
> +	v4l2_ctrl_new_std(&priv->hdl, &ov772x_ctrl_ops,
> +			V4L2_CID_VFLIP, 0, 1, 1, 0);
> +	v4l2_ctrl_new_std(&priv->hdl, &ov772x_ctrl_ops,
> +			V4L2_CID_HFLIP, 0, 1, 1, 0);
> +	v4l2_ctrl_new_std(&priv->hdl, &ov772x_ctrl_ops,
> +			V4L2_CID_BAND_STOP_FILTER, 0, 256, 1, 0);
> +	priv->subdev.ctrl_handler = &priv->hdl;
> +	if (priv->hdl.error)
> +		return priv->hdl.error;
> +
> +	priv->clk = v4l2_clk_get(&client->dev, "mclk");
> +	if (IS_ERR(priv->clk)) {
> +		ret = PTR_ERR(priv->clk);
> +		goto eclkget;
> +	}
> +
> +	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];
> +	}
> +
> +	return ret;
> +}
> +
> +static int ov772x_remove(struct i2c_client *client)
> +{
> +	struct ov772x_priv *priv = to_ov772x(i2c_get_clientdata(client));
> +
> +	v4l2_clk_put(priv->clk);
> +	v4l2_device_unregister_subdev(&priv->subdev);
> +	v4l2_ctrl_handler_free(&priv->hdl);
> +	return 0;
> +}
> +
> +static const struct i2c_device_id ov772x_id[] = {
> +	{ "ov772x", 0 },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, ov772x_id);
> +
> +static struct i2c_driver ov772x_i2c_driver = {
> +	.driver = {
> +		.name = "ov772x",
> +	},
> +	.probe    = ov772x_probe,
> +	.remove   = ov772x_remove,
> +	.id_table = ov772x_id,
> +};
> +
> +module_i2c_driver(ov772x_i2c_driver);
> +
> +MODULE_DESCRIPTION("SoC Camera driver for ov772x");

Update the description.

> +MODULE_AUTHOR("Kuninori Morimoto");

Author as well? Or add your name to it.

> +MODULE_LICENSE("GPL v2");
> 

Regards,

	Hans

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

* Re: [PATCH v1 10/10] media: i2c: tw9910: Remove soc_camera dependencies
  2017-11-15 10:56 ` [PATCH v1 10/10] media: i2c: tw9910: Remove soc_camera dependencies Jacopo Mondi
  2017-12-11 14:55   ` Laurent Pinchart
@ 2017-12-13 12:13   ` Hans Verkuil
  1 sibling, 0 replies; 56+ messages in thread
From: Hans Verkuil @ 2017-12-13 12:13 UTC (permalink / raw)
  To: Jacopo Mondi, laurent.pinchart, magnus.damm, geert, mchehab
  Cc: linux-renesas-soc, linux-media, linux-sh, linux-kernel

On 15/11/17 11:56, Jacopo Mondi wrote:
> Remove soc_camera framework dependencies from tw9910 sensor driver.
> - Handle clock directly
> - Register async subdevice
> - Add platform specific enable/disable functions
> - Adjust build system
> 
> This commit does not remove the original soc_camera based driver.
> 
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> ---
>  drivers/media/i2c/Kconfig  |  9 ++++++
>  drivers/media/i2c/Makefile |  1 +
>  drivers/media/i2c/tw9910.c | 80 ++++++++++++++++++++++++++++++++++------------
>  include/media/i2c/tw9910.h |  6 ++++
>  4 files changed, 75 insertions(+), 21 deletions(-)
> 
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index ff251ce..bbd77ee 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -415,6 +415,15 @@ config VIDEO_TW9906
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called tw9906.
> 
> +config VIDEO_TW9910
> +	tristate "Techwell TW9910 video decoder"
> +	depends on VIDEO_V4L2 && I2C
> +	---help---
> +	  Support for Techwell TW9910 NTSC/PAL/SECAM video decoder.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called tw9910.
> +
>  config VIDEO_VPX3220
>  	tristate "vpx3220a, vpx3216b & vpx3214c video decoders"
>  	depends on VIDEO_V4L2 && I2C
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index b2459a1..835784a 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -48,6 +48,7 @@ obj-$(CONFIG_VIDEO_TVP7002) += tvp7002.o
>  obj-$(CONFIG_VIDEO_TW2804) += tw2804.o
>  obj-$(CONFIG_VIDEO_TW9903) += tw9903.o
>  obj-$(CONFIG_VIDEO_TW9906) += tw9906.o
> +obj-$(CONFIG_VIDEO_TW9910) += tw9910.o
>  obj-$(CONFIG_VIDEO_CS3308) += cs3308.o
>  obj-$(CONFIG_VIDEO_CS5345) += cs5345.o
>  obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o
> diff --git a/drivers/media/i2c/tw9910.c b/drivers/media/i2c/tw9910.c
> index bdb5e0a..f422da2 100644
> --- a/drivers/media/i2c/tw9910.c
> +++ b/drivers/media/i2c/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,40 @@ 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;
> +	}
> +
> +	if (priv->clk)
> +		return clk_enable(priv->clk);
> +
> +	return 0;
> +}
> +
> +static int tw9910_power_off(struct tw9910_priv *priv)
> +{
> +	if (priv->info->platform_enable)
> +		priv->info->platform_disable();
> +
> +	if (priv->clk)
> +		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 +640,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 +825,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 +885,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 +898,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

As mentioned elsewhere, drop support for g/s_mbus_config, use the DT instead.

> @@ -935,15 +956,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 +979,27 @@ 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);
> +		goto error_put_clk;
> +
> +	ret = v4l2_async_register_subdev(&priv->subdev);
> +	if (ret)
> +		goto error_put_clk;
> +
> +	return ret;
> +
> +error_put_clk:
> +	if (priv->clk)
> +		clk_put(priv->clk);
> 
>  	return ret;
>  }
> @@ -973,7 +1007,11 @@ 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);
> +	v4l2_device_unregister_subdev(&priv->subdev);
> +
>  	return 0;
>  }
> 

Also update MODULE_DESCRIPTION.

> 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
> 

Regards,

	Hans

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

* Re: [PATCH v1 07/10] v4l: i2c: Copy ov772x soc_camera sensor driver
  2017-11-15 10:56 ` [PATCH v1 07/10] v4l: i2c: Copy ov772x soc_camera sensor driver Jacopo Mondi
  2017-12-11 14:49   ` Laurent Pinchart
  2017-12-13 12:10   ` Hans Verkuil
@ 2017-12-13 13:02   ` Philippe Ombredanne
  2 siblings, 0 replies; 56+ messages in thread
From: Philippe Ombredanne @ 2017-12-13 13:02 UTC (permalink / raw)
  To: Jacopo Mondi
  Cc: Laurent Pinchart, magnus.damm, geert, Mauro Carvalho Chehab,
	Hans Verkuil, linux-renesas-soc, Linux Media Mailing List,
	linux-sh, LKML

Jacopo,

On Wed, Nov 15, 2017 at 11:56 AM, Jacopo Mondi
<jacopo+renesas@jmondi.org> wrote:
> Copy the soc_camera based driver in v4l2 sensor driver directory.
> This commit just copies the original file without modifying it.
>
> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>

<snip>

> --- /dev/null
> +++ b/drivers/media/i2c/ov772x.c
> @@ -0,0 +1,1124 @@
> +/*
> + * ov772x Camera Driver
> + *
> + * Copyright (C) 2008 Renesas Solutions Corp.
> + * Kuninori Morimoto <morimoto.kuninori@renesas.com>
> + *
> + * Based on ov7670 and soc_camera_platform driver,
> + *
> + * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net>
> + * Copyright (C) 2008 Magnus Damm
> + * 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 version 2 as
> + * published by the Free Software Foundation.
> + */

You may want to use the new SPDX ids as documented in Thomas doc
patches instead of the loner legalese?
-- 
Cordially
Philippe Ombredanne

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-12-11 16:15   ` Laurent Pinchart
@ 2017-12-18 12:25     ` jacopo mondi
  2017-12-18 15:28       ` Laurent Pinchart
  2017-12-19 11:57     ` jacopo mondi
  1 sibling, 1 reply; 56+ messages in thread
From: jacopo mondi @ 2017-12-18 12:25 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Jacopo Mondi, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Laurent,
    thanks for review comments...

On Mon, Dec 11, 2017 at 06:15:23PM +0200, Laurent Pinchart wrote:
> Hi Jacopo,
>
> Thank you for the patch.
>
[snip]

> > +
> > +/**
> > + * ceu_buffer - Link vb2 buffer to the list of available buffers
>
> If you use kerneldoc comments please make them compile. You need to document
> the structure fields and function arguments.
>

Ok, no kernel doc for internal structures then and no kernel doc for
ugly comments you pointed out below

[snip]

> > +/**
> > + * ceu_soft_reset() - Software reset the CEU interface
> > + */
> > +static int ceu_soft_reset(struct ceu_device *ceudev)
> > +{
> > +	unsigned int reset_done;
> > +	unsigned int i;
> > +
> > +	ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CPKIL);
> > +
> > +	reset_done = 0;
> > +	for (i = 0; i < 1000 && !reset_done; i++) {
> > +		udelay(1);
> > +		if (!(ceu_read(ceudev, CEU_CSTSR) & CEU_CSTRST_CPTON))
> > +			reset_done++;
> > +	}
>
> How many iterations does this typically require ? Wouldn't a sleep be better
> than a delay ? As far as I can tell the ceu_soft_reset() function is only
> called with interrupts disabled (in interrupt context) from ceu_capture() in
> an error path, and that code should be reworked to make it possible to sleep
> if a reset takes too long.
>

The HW manual does not provide any indication about absolute timings.
I can empirically try and see, but that would just be a hint.

Also, the reset function is called in many places (runtime_pm
suspend/resume) s_stream(0) and in error path of ceu_capture().

In ceu_capture() we reset the interface if the previous frame was bad,
and we do that before re-enabling the capture interrupt (so interrupts
are not -disabled-, just the one we care about is not enabled yet..)

But that's not big deal, as if we fail there, we are about to abort
capture anyhow and so if we miss some spurious capture interrupt it's
ok...


> > +	if (!reset_done) {
> > +		v4l2_err(&ceudev->v4l2_dev, "soft reset time out\n");
>
> How about dev_err() instead ?

Is dev_err/dev_dbg preferred over v4l2_err/v4l2_dbg? Is this because
of dynamic debug?

> > +
> > +/**
> > + * 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 capture.
> > + */
> > +static int ceu_capture(struct ceu_device *ceudev)
> > +{
> > +	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> > +	dma_addr_t phys_addr_top;
> > +	u32 status;
> > +
> > +	/* Clean interrupt status and re-enable interrupts */
> > +	status = ceu_read(ceudev, CEU_CETCR);
> > +	ceu_write(ceudev, CEU_CEIER,
> > +		  ceu_read(ceudev, CEU_CEIER) & ~CEU_CEIER_MASK);
> > +	ceu_write(ceudev, CEU_CETCR, ~status & CEU_CETCR_MAGIC);
> > +	ceu_write(ceudev, CEU_CEIER, CEU_CEIER_MASK);
>
> I wonder why there's a need to disable and reenable interrupts here.

The original driver clearly said "The hardware is -very- picky about
this sequence" and I got scared and nerver touched this. Also, I very
much dislike the CEU_CETRC_MAGIC mask, but again the old driver said
"Acknoledge magical interrupt sources" and I was afraid to change it
(I can rename it though, to something lioke CEU_CETCR_ALL_INT because
that's what it is, a mask with all available interrupt source
enabled).

> > +
> > +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;
>
> Shouldn't you at least clear the interrupt source (done at the beginning of
> the ceu_capture() function) in this case ? I'd move the handling of the
> interrupt status from ceu_capture() to here and pass the status to the capture
> function. Or even handle the status here completely, as status handling isn't
> needed when ceu_capture() is called from ceu_start_streaming().

I'll try to move interrupt management here, and use flags to tell to
ceu_capture() what happened

>
> > +	/* Prepare a new 'active' buffer and trigger a new capture */
> > +	buf = vb2_to_ceu(vbuf);
> > +	vbuf->vb2_buf.timestamp = ktime_get_ns();
> > +
> > +	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;
> > +	}
> > +
> > +	/*
> > +	 * If the new capture started successfully, mark the previous buffer
> > +	 * as "DONE".
> > +	 */
> > +	ret = ceu_capture(ceudev);
> > +	if (!ret) {
> > +		vbuf->field = ceudev->field;
> > +		vbuf->sequence = ceudev->sequence++;
>
> Shouldn't you set the sequence number even when an error occurs ? You should
> also complete all buffers with VB2_BUF_STATE_ERROR in that case, as
> ceu_capture() won't start a new capture, otherwise userspace will hang
> forever.

I'll return all buffers in case of failure..

>
> > +	}
> > +
> > +	vb2_buffer_done(&vbuf->vb2_buf,
> > +			ret < 0 ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
> > +
> > +out:
> > +	spin_unlock(&ceudev->lock);
> > +
> > +	return IRQ_HANDLED;
>
> You shouldn't return IRQ_HANDLED if the IRQ status reported no interrupt.
>

Is there a case where we enter the irq handler with no interrupt?

> > + * ceu_calc_plane_sizes() - Fill 'struct v4l2_plane_pix_format' per plane
> > + *			    information according to the currently configured
> > + *			    pixel format.
> > + */
> > +static int ceu_calc_plane_sizes(struct ceu_device *ceudev,
> > +				const struct ceu_fmt *ceu_fmt,
> > +				struct v4l2_pix_format_mplane *pix)
> > +{
> > +	struct v4l2_plane_pix_format *plane_fmt = &pix->plane_fmt[0];
> > +
> > +	switch (pix->pixelformat) {
> > +	case V4L2_PIX_FMT_YUYV:
> > +		pix->num_planes			= 1;
> > +		plane_fmt[0].bytesperline	= pix->width * ceu_fmt->bpp / 8;
>
> Doesn't the driver support configurable stride ?
>
> > +		plane_fmt[0].sizeimage		= pix->height *
> > +						  plane_fmt[0].bytesperline;
>
> Padding at the end of the image should be allowed if requested by userspace.
>

Isn't stride dependent on the image format only?
Where do I find informations about userspace requested padding?

> > +
> > +	for (i = 0; i < pix->num_planes; i++) {
> > +		if (vb2_plane_size(vb, i) < pix->plane_fmt[i].sizeimage) {
> > +			v4l2_err(&ceudev->v4l2_dev,
> > +				 "Buffer #%d too small (%lu < %u)\n",
> > +				 vb->index, vb2_plane_size(vb, i),
> > +				 pix->plane_fmt[i].sizeimage);
>
> I wouldn't print an error message, otherwise userspace will have yet another
> way to flood the kernel log.

dev_dbg for dynamic_debug or drop completely?
Here and below where you pointed out the same

> > +/**
> > + * ceu_test_mbus_param() - test bus parameters against sensor provided
> > ones.
> > + *
> > + * @return: < 0 for errors
> > + *	    0 if g_mbus_config is not supported,
> > + *	    > 0  for bus configuration flags supported by (ceu AND sensor)
> > + */
> > +static int ceu_test_mbus_param(struct ceu_device *ceudev)
> > +{
> > +	struct v4l2_subdev *sd = ceudev->sd->v4l2_sd;
> > +	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;
>
> This is a legacy of soc_camera that tried to negotiate bus parameters with the
> source subdevice. We have later established that this isn't a good idea, as
> there could be components on the board that affect those settings (for
> instance inverters on the synchronization signals). This is why with DT we
> specify the bus configuration in endpoints on both sides. You should thus
> always use the bus configuration provided through DT or platform data and
> ignore the one reported by the subdev.
>

Yes, I found that when trying to implement g/s_mbus_config for ov7670
sensor. I will remove all of this and use flags returned by
"v4l2_fwnode_endpoint_parse()"
> [snip]
>
> > +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 num_sd;
> > +	int ret;
> > +
> > +	ceudev = kzalloc(sizeof(*ceudev), GFP_KERNEL);
>
> The memory is freed in ceu_vdev_release() as expected, but that will only work
> if the video device is registered. If the subdevs are never bound, the ceudev
> memory will be leaked if you unbind the CEU device from its driver. In my
> opinion this calls for registering the video device at probe time (although
> Hans disagrees).

Can I do something here to prevent this?


Thanks
   j

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-12-13 12:03   ` Hans Verkuil
@ 2017-12-18 14:12     ` jacopo mondi
  0 siblings, 0 replies; 56+ messages in thread
From: jacopo mondi @ 2017-12-18 14:12 UTC (permalink / raw)
  To: Hans Verkuil
  Cc: Jacopo Mondi, laurent.pinchart, magnus.damm, geert, mchehab,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Hans,
   thanks for review comments

On Wed, Dec 13, 2017 at 01:03:03PM +0100, Hans Verkuil wrote:
> On 15/11/17 11:55, Jacopo Mondi wrote:
> > Add driver for Renesas Capture Engine Unit (CEU).
> > +
> > +	/* 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	= v4l2_sd->ctrl_handler;
> > +	vdev->fops		= &ceu_fops;
> > +	vdev->ioctl_ops		= &ceu_ioctl_ops;
> > +	vdev->release		= ceu_vdev_release;
> > +	vdev->device_caps	= V4L2_CAP_VIDEO_CAPTURE_MPLANE |
>
> Why MPLANE? It doesn't appear to be needed since there are no multiplane
> (really: multibuffer) pixelformats defined.

The driver support NV12/21 and NV16/61 as output pixel formats (along
with single plane YUYV).

NV* formats are semi-planar, as luma is stored in one buffer, while
chrominances are stored together in a different one. Am I wrong?

> >
>
> Regards,

Thanks
   j

>
> 	Hans

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-12-18 12:25     ` jacopo mondi
@ 2017-12-18 15:28       ` Laurent Pinchart
  2017-12-21 16:27         ` jacopo mondi
  0 siblings, 1 reply; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-18 15:28 UTC (permalink / raw)
  To: jacopo mondi
  Cc: Jacopo Mondi, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Jacopo,

On Monday, 18 December 2017 14:25:12 EET jacopo mondi wrote:
> On Mon, Dec 11, 2017 at 06:15:23PM +0200, Laurent Pinchart wrote:
> > Hi Jacopo,
> > 
> > Thank you for the patch.
> 
> [snip]
> 
> >> +
> >> +/**
> >> + * ceu_buffer - Link vb2 buffer to the list of available buffers
> > 
> > If you use kerneldoc comments please make them compile. You need to
> > document the structure fields and function arguments.
> 
> Ok, no kernel doc for internal structures then and no kernel doc for
> ugly comments you pointed out below

You can use kerneldoc if you want to, but if you do please make sure it 
compiles :-)

> [snip]
> 
> >> +/**
> >> + * ceu_soft_reset() - Software reset the CEU interface
> >> + */
> >> +static int ceu_soft_reset(struct ceu_device *ceudev)
> >> +{
> >> +	unsigned int reset_done;
> >> +	unsigned int i;
> >> +
> >> +	ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CPKIL);
> >> +
> >> +	reset_done = 0;
> >> +	for (i = 0; i < 1000 && !reset_done; i++) {
> >> +		udelay(1);
> >> +		if (!(ceu_read(ceudev, CEU_CSTSR) & CEU_CSTRST_CPTON))
> >> +			reset_done++;
> >> +	}
> > 
> > How many iterations does this typically require ? Wouldn't a sleep be
> > better than a delay ? As far as I can tell the ceu_soft_reset() function
> > is only called with interrupts disabled (in interrupt context) from
> > ceu_capture() in an error path, and that code should be reworked to make
> > it possible to sleep if a reset takes too long.
> 
> The HW manual does not provide any indication about absolute timings.
> I can empirically try and see, but that would just be a hint.

That's why I asked how many iterations it typically takes :-) A hint is enough 
to start with, preferably on both SH and ARM SoCs.

> Also, the reset function is called in many places (runtime_pm
> suspend/resume) s_stream(0) and in error path of ceu_capture().
> 
> In ceu_capture() we reset the interface if the previous frame was bad,
> and we do that before re-enabling the capture interrupt (so interrupts
> are not -disabled-, just the one we care about is not enabled yet..)

The ceu_capture() function is called from the driver's interrupt handler, so 
interrupts are disabled in that code path.

> But that's not big deal, as if we fail there, we are about to abort
> capture anyhow and so if we miss some spurious capture interrupt it's
> ok...
> 
> >> +	if (!reset_done) {
> >> +		v4l2_err(&ceudev->v4l2_dev, "soft reset time out\n");
> > 
> > How about dev_err() instead ?
> 
> Is dev_err/dev_dbg preferred over v4l2_err/v4l2_dbg? Is this because
> of dynamic debug?

Yes, and the fact that the V4L2 macros don't provide us anymore with much 
compared to the dev_* macros.

> >> +
> >> +/**
> >> + * 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 capture.
> >> + */
> >> +static int ceu_capture(struct ceu_device *ceudev)
> >> +{
> >> +	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> >> +	dma_addr_t phys_addr_top;
> >> +	u32 status;
> >> +
> >> +	/* Clean interrupt status and re-enable interrupts */
> >> +	status = ceu_read(ceudev, CEU_CETCR);
> >> +	ceu_write(ceudev, CEU_CEIER,
> >> +		  ceu_read(ceudev, CEU_CEIER) & ~CEU_CEIER_MASK);
> >> +	ceu_write(ceudev, CEU_CETCR, ~status & CEU_CETCR_MAGIC);
> >> +	ceu_write(ceudev, CEU_CEIER, CEU_CEIER_MASK);
> > 
> > I wonder why there's a need to disable and reenable interrupts here.
> 
> The original driver clearly said "The hardware is -very- picky about
> this sequence" and I got scared and nerver touched this.

How about experimenting to see how the hardware reacts ?

> Also, I very much dislike the CEU_CETRC_MAGIC mask, but again the old driver
> said "Acknoledge magical interrupt sources" and I was afraid to change it
> (I can rename it though, to something lioke CEU_CETCR_ALL_INT because that's
> what it is, a mask with all available interrupt source enabled).

I think renaming it is a good idea. Additionally, regardless of whether there 
is any hidden interrupt source, the datasheet mentions for all reserved bits 
that "The write  value  should always  be 0". They should read as 0, but 
masking them would be an additional safeguard.

Also not that on the RZ/A1 platform bit 22 is documented as reserved, so you 
might want to compute the mask based on the CEU model.

If you have time you could add a debug print when an undocumented interrupt is 
flagged and see if that happens for real.

> >> +
> >> +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;
> > 
> > Shouldn't you at least clear the interrupt source (done at the beginning
> > of the ceu_capture() function) in this case ? I'd move the handling of the
> > interrupt status from ceu_capture() to here and pass the status to the
> > capture function. Or even handle the status here completely, as status
> > handling isn't needed when ceu_capture() is called from
> > ceu_start_streaming().
> 
> I'll try to move interrupt management here, and use flags to tell to
> ceu_capture() what happened
> 
> >> +	/* Prepare a new 'active' buffer and trigger a new capture */
> >> +	buf = vb2_to_ceu(vbuf);
> >> +	vbuf->vb2_buf.timestamp = ktime_get_ns();
> >> +
> >> +	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;
> >> +	}
> >> +
> >> +	/*
> >> +	 * If the new capture started successfully, mark the previous buffer
> >> +	 * as "DONE".
> >> +	 */
> >> +	ret = ceu_capture(ceudev);
> >> +	if (!ret) {
> >> +		vbuf->field = ceudev->field;
> >> +		vbuf->sequence = ceudev->sequence++;
> > 
> > Shouldn't you set the sequence number even when an error occurs ? You
> > should also complete all buffers with VB2_BUF_STATE_ERROR in that case,
> > as ceu_capture() won't start a new capture, otherwise userspace will hang
> > forever.
> 
> I'll return all buffers in case of failure..
> 
> >> +	}
> >> +
> >> +	vb2_buffer_done(&vbuf->vb2_buf,
> >> +			ret < 0 ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
> >> +
> >> +out:
> >> +	spin_unlock(&ceudev->lock);
> >> +
> >> +	return IRQ_HANDLED;
> > 
> > You shouldn't return IRQ_HANDLED if the IRQ status reported no interrupt.
> 
> Is there a case where we enter the irq handler with no interrupt?

It can happen if the IRQ is shared, which shouldn't be the case here, or if 
there's a bug somewhere, which should also not be the case :-) It's better not 
to fake it though, a large number of unhandled interrupts will cause the 
kernel to disable the CEU master interrupt, while if you make that the IRQs 
are handled the system will slow down to a freeze. Let's not short-circuit the 
safeguard mechanisms.

> >> + * ceu_calc_plane_sizes() - Fill 'struct v4l2_plane_pix_format' per
> >> plane
> >> + *			    information according to the currently configured
> >> + *			    pixel format.
> >> + */
> >> +static int ceu_calc_plane_sizes(struct ceu_device *ceudev,
> >> +				const struct ceu_fmt *ceu_fmt,
> >> +				struct v4l2_pix_format_mplane *pix)
> >> +{
> >> +	struct v4l2_plane_pix_format *plane_fmt = &pix->plane_fmt[0];
> >> +
> >> +	switch (pix->pixelformat) {
> >> +	case V4L2_PIX_FMT_YUYV:
> >> +		pix->num_planes			= 1;
> >> +		plane_fmt[0].bytesperline	= pix->width * ceu_fmt->bpp / 8;
> > 
> > Doesn't the driver support configurable stride ?
> > 
> >> +		plane_fmt[0].sizeimage		= pix->height *
> >> +						  plane_fmt[0].bytesperline;
> > 
> > Padding at the end of the image should be allowed if requested by
> > userspace.
> 
> Isn't stride dependent on the image format only?
> Where do I find informations about userspace requested padding?

Userspace can request a specific bytesperline and sizeimage value. The only 
requirement is that that device should have enough space to store the image, 
so you should increase the requested values if they are too small, but not 
decrease them.

> >> +
> >> +	for (i = 0; i < pix->num_planes; i++) {
> >> +		if (vb2_plane_size(vb, i) < pix->plane_fmt[i].sizeimage) {
> >> +			v4l2_err(&ceudev->v4l2_dev,
> >> +				 "Buffer #%d too small (%lu < %u)\n",
> >> +				 vb->index, vb2_plane_size(vb, i),
> >> +				 pix->plane_fmt[i].sizeimage);
> > 
> > I wouldn't print an error message, otherwise userspace will have yet
> > another way to flood the kernel log.
> 
> dev_dbg for dynamic_debug or drop completely?
> Here and below where you pointed out the same

I'd drop them completely.

> >> +/**
> >> + * ceu_test_mbus_param() - test bus parameters against sensor provided
> >> ones.
> >> + *
> >> + * @return: < 0 for errors
> >> + *	    0 if g_mbus_config is not supported,
> >> + *	    > 0  for bus configuration flags supported by (ceu AND sensor)
> >> + */
> >> +static int ceu_test_mbus_param(struct ceu_device *ceudev)
> >> +{
> >> +	struct v4l2_subdev *sd = ceudev->sd->v4l2_sd;
> >> +	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;
> > 
> > This is a legacy of soc_camera that tried to negotiate bus parameters with
> > the source subdevice. We have later established that this isn't a good
> > idea, as there could be components on the board that affect those
> > settings (for instance inverters on the synchronization signals). This is
> > why with DT we specify the bus configuration in endpoints on both sides.
> > You should thus always use the bus configuration provided through DT or
> > platform data and ignore the one reported by the subdev.
> 
> Yes, I found that when trying to implement g/s_mbus_config for ov7670
> sensor. I will remove all of this and use flags returned by
> "v4l2_fwnode_endpoint_parse()"
> 
> > [snip]
> > 
> >> +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 num_sd;
> >> +	int ret;
> >> +
> >> +	ceudev = kzalloc(sizeof(*ceudev), GFP_KERNEL);
> > 
> > The memory is freed in ceu_vdev_release() as expected, but that will only
> > work if the video device is registered. If the subdevs are never bound,
> > the ceudev memory will be leaked if you unbind the CEU device from its
> > driver. In my opinion this calls for registering the video device at
> > probe time (although Hans disagrees).
> 
> Can I do something here to prevent this?

You can register the video node in the probe function ;-) It's a framework 
problem, we need to agree on a solution there before pushing it down to 
drivers.

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-12-11 16:15   ` Laurent Pinchart
  2017-12-18 12:25     ` jacopo mondi
@ 2017-12-19 11:57     ` jacopo mondi
  2017-12-19 13:07       ` Laurent Pinchart
  1 sibling, 1 reply; 56+ messages in thread
From: jacopo mondi @ 2017-12-19 11:57 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Jacopo Mondi, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Laurent,
   a few more details on subdevice management

On Mon, Dec 11, 2017 at 06:15:23PM +0200, Laurent Pinchart wrote:
> Hi Jacopo,
>
> Thank you for the patch.
>
> [snip]
>
> > +static int ceu_sensor_bound(struct v4l2_async_notifier *notifier,
> > +			    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(asd);
> > +
> > +	if (video_is_registered(&ceudev->vdev)) {
> > +		v4l2_err(&ceudev->v4l2_dev,
> > +			 "Video device registered before this sub-device.\n");
> > +		return -EBUSY;
>
> Can this happen ?
>
> > +	}
> > +
> > +	/* Assign subdevices in the order they appear */
> > +	ceu_sd->v4l2_sd = v4l2_sd;
> > +	ceudev->num_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 */
> > +	q->type			= V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> > +	q->io_modes		= VB2_MMAP | VB2_USERPTR;
>
> No dmabuf ?
>
> > +	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;
>
> [snip]
>
> > +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 num_sd;
> > +	int ret;
> > +
> > +	ceudev = kzalloc(sizeof(*ceudev), GFP_KERNEL);
>
> The memory is freed in ceu_vdev_release() as expected, but that will only work
> if the video device is registered. If the subdevs are never bound, the ceudev
> memory will be leaked if you unbind the CEU device from its driver. In my
> opinion this calls for registering the video device at probe time (although
> Hans disagrees).
>
> > +	if (!ceudev)
> > +		return -ENOMEM;
> > +
> > +	platform_set_drvdata(pdev, ceudev);
> > +	dev_set_drvdata(dev, ceudev);
>
> You don't need the second line, platform_set_drvdata() is a wrapper around
> dev_set_drvdata().
>
> > +	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);
> > +	if (IS_ERR(res))
> > +		return PTR_ERR(res);
>
> No need for error handling here, devm_ioremap_resource() will check the res
> pointer.
>
> > +	base = devm_ioremap_resource(dev, res);
>
> You can assign ceudev->base directly and remove the base local variable.
>
> > +	if (IS_ERR(base))
> > +		return PTR_ERR(base);
> > +	ceudev->base = base;
> > +
> > +	ret = platform_get_irq(pdev, 0);
> > +	if (ret < 0) {
> > +		dev_err(dev, "failed to get irq: %d\n", ret);
> > +		return ret;
> > +	}
> > +	irq = ret;
> > +
> > +	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;
> > +	}
> > +
> > +	pm_suspend_ignore_children(dev, true);
> > +	pm_runtime_enable(dev);
> > +
> > +	ret = v4l2_device_register(dev, &ceudev->v4l2_dev);
> > +	if (ret)
> > +		goto error_pm_disable;
> > +
> > +	if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
> > +		num_sd = ceu_parse_dt(ceudev);
> > +	} else if (dev->platform_data) {
> > +		num_sd = ceu_parse_platform_data(ceudev, dev->platform_data);
> > +	} else {
> > +		dev_err(dev, "CEU platform data not set and no OF support\n");
> > +		ret = -EINVAL;
> > +		goto error_v4l2_unregister;
> > +	}
> > +
> > +	if (num_sd < 0) {
> > +		ret = num_sd;
> > +		goto error_v4l2_unregister;
> > +	} else if (num_sd == 0)
> > +		return 0;
>
> You need braces around the second statement too.

Ok, actually parse_dt() and parse_platform_data() behaves differently.
The former returns error if no subdevices are connected to CEU, the
latter returns 0. That's wrong.

I wonder what's the correct behavior here. Other mainline drivers I
looked into (pxa_camera and atmel-isc) behaves differently from each
other, so I guess this is up to each platform to decide.

Also, the CEU can accept one single input (and I made it clear
in DT bindings documentation saying it accepts a single endpoint,
while I'm parsing all the available ones in driver, I will fix this)
but as it happens on Migo-R, there could be HW hacks to share the input
lines between multiple subdevices. Should I accept it from dts as well?

So:
1) Should we fail to probe if no subdevices are connected?
2) Should we accept more than 1 subdevice from dts as it happens right
now for platform data?

Thanks
   j

>
> [snip]
>
> > +static const struct dev_pm_ops ceu_pm_ops = {
> > +	SET_RUNTIME_PM_OPS(ceu_runtime_suspend,
> > +			   ceu_runtime_resume,
> > +			   NULL)
>
> You'll probably need system PM ops eventually, but for now this isn't a
> regression so I won't complain too much.
>
> > +};
>
> [snip]
>
> --
> Regards,
>
> Laurent Pinchart
>

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-12-19 11:57     ` jacopo mondi
@ 2017-12-19 13:07       ` Laurent Pinchart
  2017-12-19 13:28         ` Sakari Ailus
  0 siblings, 1 reply; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-19 13:07 UTC (permalink / raw)
  To: jacopo mondi
  Cc: Jacopo Mondi, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel,
	sakari.ailus

Hi Jacopo,

(CC'ing Sakari)

On Tuesday, 19 December 2017 13:57:42 EET jacopo mondi wrote:
> On Mon, Dec 11, 2017 at 06:15:23PM +0200, Laurent Pinchart wrote:
> > Hi Jacopo,
> > 
> > Thank you for the patch.
> > 
> > [snip]
> > 
> >> +static int ceu_sensor_bound(struct v4l2_async_notifier *notifier,
> >> +			    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(asd);
> >> +
> >> +	if (video_is_registered(&ceudev->vdev)) {
> >> +		v4l2_err(&ceudev->v4l2_dev,
> >> +			 "Video device registered before this sub-device.\n");
> >> +		return -EBUSY;
> > 
> > Can this happen ?
> > 
> >> +	}
> >> +
> >> +	/* Assign subdevices in the order they appear */
> >> +	ceu_sd->v4l2_sd = v4l2_sd;
> >> +	ceudev->num_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 */
> > > +	q->type			= V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> > > +	q->io_modes		= VB2_MMAP | VB2_USERPTR;
> > 
> > No dmabuf ?
> > 
> > > +	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;
> > 
> > [snip]
> > 
> > > +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 num_sd;
> > > +	int ret;
> > > +
> > > +	ceudev = kzalloc(sizeof(*ceudev), GFP_KERNEL);
> > 
> > The memory is freed in ceu_vdev_release() as expected, but that will only
> > work if the video device is registered. If the subdevs are never bound,
> > the ceudev memory will be leaked if you unbind the CEU device from its
> > driver. In my opinion this calls for registering the video device at
> > probe time (although Hans disagrees).
> > 
> > > +	if (!ceudev)
> > > +		return -ENOMEM;
> > > +
> > > +	platform_set_drvdata(pdev, ceudev);
> > > +	dev_set_drvdata(dev, ceudev);
> > 
> > You don't need the second line, platform_set_drvdata() is a wrapper around
> > dev_set_drvdata().
> > 
> > > +	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);
> > > +	if (IS_ERR(res))
> > > +		return PTR_ERR(res);
> > 
> > No need for error handling here, devm_ioremap_resource() will check the
> > res
> > pointer.
> > 
> > > +	base = devm_ioremap_resource(dev, res);
> > 
> > You can assign ceudev->base directly and remove the base local variable.
> > 
> > > +	if (IS_ERR(base))
> > > +		return PTR_ERR(base);
> > > +	ceudev->base = base;
> > > +
> > > +	ret = platform_get_irq(pdev, 0);
> > > +	if (ret < 0) {
> > > +		dev_err(dev, "failed to get irq: %d\n", ret);
> > > +		return ret;
> > > +	}
> > > +	irq = ret;
> > > +
> > > +	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;
> > > +	}
> > > +
> > > +	pm_suspend_ignore_children(dev, true);
> > > +	pm_runtime_enable(dev);
> > > +
> > > +	ret = v4l2_device_register(dev, &ceudev->v4l2_dev);
> > > +	if (ret)
> > > +		goto error_pm_disable;
> > > +
> > > +	if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
> > > +		num_sd = ceu_parse_dt(ceudev);
> > > +	} else if (dev->platform_data) {
> > > +		num_sd = ceu_parse_platform_data(ceudev, dev->platform_data);
> > > +	} else {
> > > +		dev_err(dev, "CEU platform data not set and no OF support\n");
> > > +		ret = -EINVAL;
> > > +		goto error_v4l2_unregister;
> > > +	}
> > > +
> > > +	if (num_sd < 0) {
> > > +		ret = num_sd;
> > > +		goto error_v4l2_unregister;
> > > +	} else if (num_sd == 0)
> > > +		return 0;
> > 
> > You need braces around the second statement too.
> 
> Ok, actually parse_dt() and parse_platform_data() behaves differently.
> The former returns error if no subdevices are connected to CEU, the
> latter returns 0. That's wrong.
> 
> I wonder what's the correct behavior here. Other mainline drivers I
> looked into (pxa_camera and atmel-isc) behaves differently from each
> other, so I guess this is up to each platform to decide.

No, what it means is that we've failed to standardize it, not that it 
shouldn't be standardized :-)

> Also, the CEU can accept one single input (and I made it clear
> in DT bindings documentation saying it accepts a single endpoint,
> while I'm parsing all the available ones in driver, I will fix this)
> but as it happens on Migo-R, there could be HW hacks to share the input
> lines between multiple subdevices. Should I accept it from dts as well?
> 
> So:
> 1) Should we fail to probe if no subdevices are connected?

While the CEU itself would be fully functional without a subdev, in practice 
it would be of no use. I would thus fail probing.

> 2) Should we accept more than 1 subdevice from dts as it happens right
> now for platform data?

We need to support multiple connected devices, as some of the boards require 
that. What I'm not sure about is whether the multiplexer on the Migo-R board 
should be modeled as a subdevice. We could in theory connect multiple sensors 
to the CEU input signals without any multiplexer as long as all but one are in 
reset with their outputs in a high impedance state. As that wouldn' require a 
multiplexer we would need to support multiple endpoints in the CEU port. We 
could then support Migo-R the same way, making the multiplexer transparent.

Sakari, what would you do here ?

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-12-19 13:07       ` Laurent Pinchart
@ 2017-12-19 13:28         ` Sakari Ailus
  2017-12-19 13:52           ` Laurent Pinchart
  0 siblings, 1 reply; 56+ messages in thread
From: Sakari Ailus @ 2017-12-19 13:28 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: jacopo mondi, Jacopo Mondi, magnus.damm, geert, mchehab,
	hverkuil, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Heippa!

On Tue, Dec 19, 2017 at 03:07:41PM +0200, Laurent Pinchart wrote:
> Hi Jacopo,
> 
> (CC'ing Sakari)
> 
> On Tuesday, 19 December 2017 13:57:42 EET jacopo mondi wrote:
> > On Mon, Dec 11, 2017 at 06:15:23PM +0200, Laurent Pinchart wrote:
> > > Hi Jacopo,
> > > 
> > > Thank you for the patch.
> > > 
> > > [snip]
> > > 
> > >> +static int ceu_sensor_bound(struct v4l2_async_notifier *notifier,
> > >> +			    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(asd);
> > >> +
> > >> +	if (video_is_registered(&ceudev->vdev)) {
> > >> +		v4l2_err(&ceudev->v4l2_dev,
> > >> +			 "Video device registered before this sub-device.\n");
> > >> +		return -EBUSY;
> > > 
> > > Can this happen ?
> > > 
> > >> +	}
> > >> +
> > >> +	/* Assign subdevices in the order they appear */
> > >> +	ceu_sd->v4l2_sd = v4l2_sd;
> > >> +	ceudev->num_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 */
> > > > +	q->type			= V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> > > > +	q->io_modes		= VB2_MMAP | VB2_USERPTR;
> > > 
> > > No dmabuf ?
> > > 
> > > > +	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;
> > > 
> > > [snip]
> > > 
> > > > +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 num_sd;
> > > > +	int ret;
> > > > +
> > > > +	ceudev = kzalloc(sizeof(*ceudev), GFP_KERNEL);
> > > 
> > > The memory is freed in ceu_vdev_release() as expected, but that will only
> > > work if the video device is registered. If the subdevs are never bound,
> > > the ceudev memory will be leaked if you unbind the CEU device from its
> > > driver. In my opinion this calls for registering the video device at
> > > probe time (although Hans disagrees).
> > > 
> > > > +	if (!ceudev)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	platform_set_drvdata(pdev, ceudev);
> > > > +	dev_set_drvdata(dev, ceudev);
> > > 
> > > You don't need the second line, platform_set_drvdata() is a wrapper around
> > > dev_set_drvdata().
> > > 
> > > > +	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);
> > > > +	if (IS_ERR(res))
> > > > +		return PTR_ERR(res);
> > > 
> > > No need for error handling here, devm_ioremap_resource() will check the
> > > res
> > > pointer.
> > > 
> > > > +	base = devm_ioremap_resource(dev, res);
> > > 
> > > You can assign ceudev->base directly and remove the base local variable.
> > > 
> > > > +	if (IS_ERR(base))
> > > > +		return PTR_ERR(base);
> > > > +	ceudev->base = base;
> > > > +
> > > > +	ret = platform_get_irq(pdev, 0);
> > > > +	if (ret < 0) {
> > > > +		dev_err(dev, "failed to get irq: %d\n", ret);
> > > > +		return ret;
> > > > +	}
> > > > +	irq = ret;
> > > > +
> > > > +	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;
> > > > +	}
> > > > +
> > > > +	pm_suspend_ignore_children(dev, true);
> > > > +	pm_runtime_enable(dev);
> > > > +
> > > > +	ret = v4l2_device_register(dev, &ceudev->v4l2_dev);
> > > > +	if (ret)
> > > > +		goto error_pm_disable;
> > > > +
> > > > +	if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
> > > > +		num_sd = ceu_parse_dt(ceudev);
> > > > +	} else if (dev->platform_data) {
> > > > +		num_sd = ceu_parse_platform_data(ceudev, dev->platform_data);
> > > > +	} else {
> > > > +		dev_err(dev, "CEU platform data not set and no OF support\n");
> > > > +		ret = -EINVAL;
> > > > +		goto error_v4l2_unregister;
> > > > +	}
> > > > +
> > > > +	if (num_sd < 0) {
> > > > +		ret = num_sd;
> > > > +		goto error_v4l2_unregister;
> > > > +	} else if (num_sd == 0)
> > > > +		return 0;
> > > 
> > > You need braces around the second statement too.
> > 
> > Ok, actually parse_dt() and parse_platform_data() behaves differently.
> > The former returns error if no subdevices are connected to CEU, the
> > latter returns 0. That's wrong.
> > 
> > I wonder what's the correct behavior here. Other mainline drivers I
> > looked into (pxa_camera and atmel-isc) behaves differently from each
> > other, so I guess this is up to each platform to decide.
> 
> No, what it means is that we've failed to standardize it, not that it 
> shouldn't be standardized :-)
> 
> > Also, the CEU can accept one single input (and I made it clear
> > in DT bindings documentation saying it accepts a single endpoint,
> > while I'm parsing all the available ones in driver, I will fix this)
> > but as it happens on Migo-R, there could be HW hacks to share the input
> > lines between multiple subdevices. Should I accept it from dts as well?
> > 
> > So:
> > 1) Should we fail to probe if no subdevices are connected?
> 
> While the CEU itself would be fully functional without a subdev, in practice 
> it would be of no use. I would thus fail probing.
> 
> > 2) Should we accept more than 1 subdevice from dts as it happens right
> > now for platform data?
> 
> We need to support multiple connected devices, as some of the boards require 
> that. What I'm not sure about is whether the multiplexer on the Migo-R board 
> should be modeled as a subdevice. We could in theory connect multiple sensors 
> to the CEU input signals without any multiplexer as long as all but one are in 
> reset with their outputs in a high impedance state. As that wouldn' require a 
> multiplexer we would need to support multiple endpoints in the CEU port. We 
> could then support Migo-R the same way, making the multiplexer transparent.
> 
> Sakari, what would you do here ?

We do have:

drivers/media/platform/video-mux.c

What is not addressed right now are the CSI-2 bus parameters, if the mux is
just a passive switch. This could be done using the frame descriptors.

-- 
Sakari Ailus
e-mail: sakari.ailus@iki.fi

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-12-19 13:28         ` Sakari Ailus
@ 2017-12-19 13:52           ` Laurent Pinchart
  0 siblings, 0 replies; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-19 13:52 UTC (permalink / raw)
  To: Sakari Ailus
  Cc: jacopo mondi, Jacopo Mondi, magnus.damm, geert, mchehab,
	hverkuil, linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Sakari,

On Tuesday, 19 December 2017 15:28:55 EET Sakari Ailus wrote:
> On Tue, Dec 19, 2017 at 03:07:41PM +0200, Laurent Pinchart wrote:
> > On Tuesday, 19 December 2017 13:57:42 EET jacopo mondi wrote:

[snip]

> >> Ok, actually parse_dt() and parse_platform_data() behaves differently.
> >> The former returns error if no subdevices are connected to CEU, the
> >> latter returns 0. That's wrong.
> >> 
> >> I wonder what's the correct behavior here. Other mainline drivers I
> >> looked into (pxa_camera and atmel-isc) behaves differently from each
> >> other, so I guess this is up to each platform to decide.
> > 
> > No, what it means is that we've failed to standardize it, not that it
> > shouldn't be standardized :-)
> > 
> >> Also, the CEU can accept one single input (and I made it clear
> >> in DT bindings documentation saying it accepts a single endpoint,
> >> while I'm parsing all the available ones in driver, I will fix this)
> >> but as it happens on Migo-R, there could be HW hacks to share the input
> >> lines between multiple subdevices. Should I accept it from dts as well?
> >> 
> >> So:
> >> 1) Should we fail to probe if no subdevices are connected?
> > 
> > While the CEU itself would be fully functional without a subdev, in
> > practice it would be of no use. I would thus fail probing.
> > 
> >> 2) Should we accept more than 1 subdevice from dts as it happens right
> >> now for platform data?
> > 
> > We need to support multiple connected devices, as some of the boards
> > require that. What I'm not sure about is whether the multiplexer on the
> > Migo-R board should be modeled as a subdevice. We could in theory connect
> > multiple sensors to the CEU input signals without any multiplexer as long
> > as all but one are in reset with their outputs in a high impedance state.
> > As that wouldn' require a multiplexer we would need to support multiple
> > endpoints in the CEU port. We could then support Migo-R the same way,
> > making the multiplexer transparent.
> > 
> > Sakari, what would you do here ?
> 
> We do have:
> 
> drivers/media/platform/video-mux.c
> 
> What is not addressed right now are the CSI-2 bus parameters, if the mux is
> just a passive switch. This could be done using the frame descriptors.

We're talking about a parallel bus here so that shouldn't be a problem.

Our issue is that the same GPIO controls both the switch and the power down 
signal of one of the sensors. The hardware has been designed to be as 
transparent as possible, but that creates issues as Linux doesn't support 
share GPIOs.

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-12-18 15:28       ` Laurent Pinchart
@ 2017-12-21 16:27         ` jacopo mondi
  2017-12-22 12:03           ` Laurent Pinchart
  0 siblings, 1 reply; 56+ messages in thread
From: jacopo mondi @ 2017-12-21 16:27 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Jacopo Mondi, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Laurent,

On Mon, Dec 18, 2017 at 05:28:43PM +0200, Laurent Pinchart wrote:
> Hi Jacopo,
>
> On Monday, 18 December 2017 14:25:12 EET jacopo mondi wrote:
> > On Mon, Dec 11, 2017 at 06:15:23PM +0200, Laurent Pinchart wrote:
[snip]
> > >> +/**
> > >> + * ceu_soft_reset() - Software reset the CEU interface
> > >> + */
> > >> +static int ceu_soft_reset(struct ceu_device *ceudev)
> > >> +{
> > >> +	unsigned int reset_done;
> > >> +	unsigned int i;
> > >> +
> > >> +	ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CPKIL);
> > >> +
> > >> +	reset_done = 0;
> > >> +	for (i = 0; i < 1000 && !reset_done; i++) {
> > >> +		udelay(1);
> > >> +		if (!(ceu_read(ceudev, CEU_CSTSR) & CEU_CSTRST_CPTON))
> > >> +			reset_done++;
> > >> +	}
> > >
> > > How many iterations does this typically require ? Wouldn't a sleep be
> > > better than a delay ? As far as I can tell the ceu_soft_reset() function
> > > is only called with interrupts disabled (in interrupt context) from
> > > ceu_capture() in an error path, and that code should be reworked to make
> > > it possible to sleep if a reset takes too long.
> >
> > The HW manual does not provide any indication about absolute timings.
> > I can empirically try and see, but that would just be a hint.
>
> That's why I asked how many iterations it typically takes :-) A hint is enough
> to start with, preferably on both SH and ARM SoCs.

I've seen only 0s when printing out how many cycles it takes to clear
both registers. This means 1usec is enough, therefore I would keep using
udelay here. Also, I would reduce the attempts to 100 here (or even
10), as if a single one is typically enough, 1000 is definitely an
overkill.

>
> > Also, the reset function is called in many places (runtime_pm
> > suspend/resume) s_stream(0) and in error path of ceu_capture().
> >
> > In ceu_capture() we reset the interface if the previous frame was bad,
> > and we do that before re-enabling the capture interrupt (so interrupts
> > are not -disabled-, just the one we care about is not enabled yet..)
>
> The ceu_capture() function is called from the driver's interrupt handler, so
> interrupts are disabled in that code path.
>

I have removed that reset call from capture and re-worked the irq
handler to manage state before calling capture().

> > But that's not big deal, as if we fail there, we are about to abort
> > capture anyhow and so if we miss some spurious capture interrupt it's
> > ok...
> >
> > >> +	if (!reset_done) {
> > >> +		v4l2_err(&ceudev->v4l2_dev, "soft reset time out\n");
> > >
> > > How about dev_err() instead ?
> >
> > Is dev_err/dev_dbg preferred over v4l2_err/v4l2_dbg? Is this because
> > of dynamic debug?
>
> Yes, and the fact that the V4L2 macros don't provide us anymore with much
> compared to the dev_* macros.
>
> > >> +
> > >> +/**
> > >> + * 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 capture.
> > >> + */
> > >> +static int ceu_capture(struct ceu_device *ceudev)
> > >> +{
> > >> +	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> > >> +	dma_addr_t phys_addr_top;
> > >> +	u32 status;
> > >> +
> > >> +	/* Clean interrupt status and re-enable interrupts */
> > >> +	status = ceu_read(ceudev, CEU_CETCR);
> > >> +	ceu_write(ceudev, CEU_CEIER,
> > >> +		  ceu_read(ceudev, CEU_CEIER) & ~CEU_CEIER_MASK);
> > >> +	ceu_write(ceudev, CEU_CETCR, ~status & CEU_CETCR_MAGIC);
> > >> +	ceu_write(ceudev, CEU_CEIER, CEU_CEIER_MASK);
> > >
> > > I wonder why there's a need to disable and reenable interrupts here.
> >
> > The original driver clearly said "The hardware is -very- picky about
> > this sequence" and I got scared and nerver touched this.
>
> How about experimenting to see how the hardware reacts ?

Turns out this was not needed at all, both on RZ and SH4. I captured
several images without any issues in both platforms just clearing the
interrupt state without disabling interrutps.

>
> > Also, I very much dislike the CEU_CETRC_MAGIC mask, but again the old driver
> > said "Acknoledge magical interrupt sources" and I was afraid to change it
> > (I can rename it though, to something lioke CEU_CETCR_ALL_INT because that's
> > what it is, a mask with all available interrupt source enabled).
>
> I think renaming it is a good idea. Additionally, regardless of whether there
> is any hidden interrupt source, the datasheet mentions for all reserved bits
> that "The write  value  should always  be 0". They should read as 0, but
> masking them would be an additional safeguard.

The HW manual is a bit confused (and confusing) on this point.
Yes, there is the statement you have cited here, but there's also
"to clear only the CPE bit to 0, write H'FFFF FFFE to CETCR" a few
lines above, which clearly contradicts the "write 0 to reserved bits"
thing.

In practice, I'm now writing to 0 only bits to be cleared, and thus
writing 1s to everything else, reserved included. I haven't seen any
issue both on RZ and SH4 platforms.

>
> Also not that on the RZ/A1 platform bit 22 is documented as reserved, so you
> might want to compute the mask based on the CEU model.

While I can use the .data pointer of 'of_device_id' for OF based
devices (RZ) to store the opportune IRQ mask, I'm not sure how to
do that for platform devices. Can I assume (platform data == SH) in
you opinion?

>
> If you have time you could add a debug print when an undocumented interrupt is
> flagged and see if that happens for real.

It's not "undocumented interrupt sources" but "magical interrupt
sources". It's very different :-D

By the way, no, never seen any!

Thanks
   j

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-12-21 16:27         ` jacopo mondi
@ 2017-12-22 12:03           ` Laurent Pinchart
  2017-12-22 14:40             ` jacopo mondi
  0 siblings, 1 reply; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-22 12:03 UTC (permalink / raw)
  To: jacopo mondi
  Cc: Jacopo Mondi, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Jacopo,

On Thursday, 21 December 2017 18:27:02 EET jacopo mondi wrote:
> On Mon, Dec 18, 2017 at 05:28:43PM +0200, Laurent Pinchart wrote:
> > On Monday, 18 December 2017 14:25:12 EET jacopo mondi wrote:
> >> On Mon, Dec 11, 2017 at 06:15:23PM +0200, Laurent Pinchart wrote:
> > 
> [snip]
> 
> >>>> +/**
> >>>> + * ceu_soft_reset() - Software reset the CEU interface
> >>>> + */
> >>>> +static int ceu_soft_reset(struct ceu_device *ceudev)
> >>>> +{
> >>>> +	unsigned int reset_done;
> >>>> +	unsigned int i;
> >>>> +
> >>>> +	ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CPKIL);
> >>>> +
> >>>> +	reset_done = 0;
> >>>> +	for (i = 0; i < 1000 && !reset_done; i++) {
> >>>> +		udelay(1);
> >>>> +		if (!(ceu_read(ceudev, CEU_CSTSR) & CEU_CSTRST_CPTON))
> >>>> +			reset_done++;
> >>>> +	}
> >>> 
> >>> How many iterations does this typically require ? Wouldn't a sleep be
> >>> better than a delay ? As far as I can tell the ceu_soft_reset()
> >>> function is only called with interrupts disabled (in interrupt context)
> >>> from ceu_capture() in an error path, and that code should be reworked
> >>> to make it possible to sleep if a reset takes too long.
> >> 
> >> The HW manual does not provide any indication about absolute timings.
> >> I can empirically try and see, but that would just be a hint.
> > 
> > That's why I asked how many iterations it typically takes :-) A hint is
> > enough to start with, preferably on both SH and ARM SoCs.
> 
> I've seen only 0s when printing out how many cycles it takes to clear
> both registers. This means 1usec is enough, therefore I would keep using
> udelay here. Also, I would reduce the attempts to 100 here (or even
> 10), as if a single one is typically enough, 1000 is definitely an
> overkill.

I'd go for 10. This being said, please make sure you run tests where the reset 
is performed during capture in the middle of a frame, to see if it changes the 
number of iterations.

> >> Also, the reset function is called in many places (runtime_pm
> >> suspend/resume) s_stream(0) and in error path of ceu_capture().
> >> 
> >> In ceu_capture() we reset the interface if the previous frame was bad,
> >> and we do that before re-enabling the capture interrupt (so interrupts
> >> are not -disabled-, just the one we care about is not enabled yet..)
> > 
> > The ceu_capture() function is called from the driver's interrupt handler,
> > so interrupts are disabled in that code path.
> 
> I have removed that reset call from capture and re-worked the irq
> handler to manage state before calling capture().
> 
> >> But that's not big deal, as if we fail there, we are about to abort
> >> capture anyhow and so if we miss some spurious capture interrupt it's
> >> ok...
> >> 
> >> >> +	if (!reset_done) {
> >> >> +		v4l2_err(&ceudev->v4l2_dev, "soft reset time out\n");
> >> > 
> >> > How about dev_err() instead ?
> >> 
> >> Is dev_err/dev_dbg preferred over v4l2_err/v4l2_dbg? Is this because
> >> of dynamic debug?
> > 
> > Yes, and the fact that the V4L2 macros don't provide us anymore with much
> > compared to the dev_* macros.
> > 
> >>>> +
> >>>> +/**
> >>>> + * 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 capture.
> >>>> + */
> >>>> +static int ceu_capture(struct ceu_device *ceudev)
> >>>> +{
> >>>> +	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> >>>> +	dma_addr_t phys_addr_top;
> >>>> +	u32 status;
> >>>> +
> >>>> +	/* Clean interrupt status and re-enable interrupts */
> >>>> +	status = ceu_read(ceudev, CEU_CETCR);
> >>>> +	ceu_write(ceudev, CEU_CEIER,
> >>>> +		  ceu_read(ceudev, CEU_CEIER) & ~CEU_CEIER_MASK);
> >>>> +	ceu_write(ceudev, CEU_CETCR, ~status & CEU_CETCR_MAGIC);
> >>>> +	ceu_write(ceudev, CEU_CEIER, CEU_CEIER_MASK);
> >>> 
> >>> I wonder why there's a need to disable and reenable interrupts here.
> >> 
> >> The original driver clearly said "The hardware is -very- picky about
> >> this sequence" and I got scared and nerver touched this.
> > 
> > How about experimenting to see how the hardware reacts ?
> 
> Turns out this was not needed at all, both on RZ and SH4. I captured
> several images without any issues in both platforms just clearing the
> interrupt state without disabling interrutps.

I wonder whether it could cause an issue when interrupts are raised by the 
hardware at the same time they are cleared by the driver. That's hard to test 
though.

What happens when an interrupt source is masked by the CEIER register, is it 
still reported by the status CETCR register (obviously without raising an 
interrupt to the CPU), or does it not get flagged at all ?

> >> Also, I very much dislike the CEU_CETRC_MAGIC mask, but again the old
> >> driver said "Acknoledge magical interrupt sources" and I was afraid to
> >> change it (I can rename it though, to something lioke CEU_CETCR_ALL_INT
> >> because that's what it is, a mask with all available interrupt source
> >> enabled).
> > 
> > I think renaming it is a good idea. Additionally, regardless of whether
> > there is any hidden interrupt source, the datasheet mentions for all
> > reserved bits that "The write  value  should always  be 0". They should
> > read as 0, but masking them would be an additional safeguard.
> 
> The HW manual is a bit confused (and confusing) on this point.
> Yes, there is the statement you have cited here, but there's also
> "to clear only the CPE bit to 0, write H'FFFF FFFE to CETCR" a few
> lines above, which clearly contradicts the "write 0 to reserved bits"
> thing.
> 
> In practice, I'm now writing to 0 only bits to be cleared, and thus
> writing 1s to everything else, reserved included. I haven't seen any
> issue both on RZ and SH4 platforms.
> 
> > Also not that on the RZ/A1 platform bit 22 is documented as reserved, so
> > you might want to compute the mask based on the CEU model.
> 
> While I can use the .data pointer of 'of_device_id' for OF based
> devices (RZ) to store the opportune IRQ mask, I'm not sure how to
> do that for platform devices. Can I assume (platform data == SH) in
> you opinion?

Yes you can.

> > If you have time you could add a debug print when an undocumented
> > interrupt is flagged and see if that happens for real.
> 
> It's not "undocumented interrupt sources" but "magical interrupt
> sources". It's very different :-D

:-)

> By the way, no, never seen any!

-- 
Regards,

Laurent Pinchart

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-12-22 12:03           ` Laurent Pinchart
@ 2017-12-22 14:40             ` jacopo mondi
  2017-12-23 11:39               ` Laurent Pinchart
  0 siblings, 1 reply; 56+ messages in thread
From: jacopo mondi @ 2017-12-22 14:40 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Jacopo Mondi, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Laurent,

On Fri, Dec 22, 2017 at 02:03:41PM +0200, Laurent Pinchart wrote:
> Hi Jacopo,
>
> On Thursday, 21 December 2017 18:27:02 EET jacopo mondi wrote:
> > On Mon, Dec 18, 2017 at 05:28:43PM +0200, Laurent Pinchart wrote:
> > > On Monday, 18 December 2017 14:25:12 EET jacopo mondi wrote:
> > >> On Mon, Dec 11, 2017 at 06:15:23PM +0200, Laurent Pinchart wrote:
> > >
> > [snip]
> >
> > >>>> +/**
> > >>>> + * ceu_soft_reset() - Software reset the CEU interface
> > >>>> + */
> > >>>> +static int ceu_soft_reset(struct ceu_device *ceudev)
> > >>>> +{
> > >>>> +	unsigned int reset_done;
> > >>>> +	unsigned int i;
> > >>>> +
> > >>>> +	ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CPKIL);
> > >>>> +
> > >>>> +	reset_done = 0;
> > >>>> +	for (i = 0; i < 1000 && !reset_done; i++) {
> > >>>> +		udelay(1);
> > >>>> +		if (!(ceu_read(ceudev, CEU_CSTSR) & CEU_CSTRST_CPTON))
> > >>>> +			reset_done++;
> > >>>> +	}
> > >>>
> > >>> How many iterations does this typically require ? Wouldn't a sleep be
> > >>> better than a delay ? As far as I can tell the ceu_soft_reset()
> > >>> function is only called with interrupts disabled (in interrupt context)
> > >>> from ceu_capture() in an error path, and that code should be reworked
> > >>> to make it possible to sleep if a reset takes too long.
> > >>
> > >> The HW manual does not provide any indication about absolute timings.
> > >> I can empirically try and see, but that would just be a hint.
> > >
> > > That's why I asked how many iterations it typically takes :-) A hint is
> > > enough to start with, preferably on both SH and ARM SoCs.
> >
> > I've seen only 0s when printing out how many cycles it takes to clear
> > both registers. This means 1usec is enough, therefore I would keep using
> > udelay here. Also, I would reduce the attempts to 100 here (or even
> > 10), as if a single one is typically enough, 1000 is definitely an
> > overkill.
>
> I'd go for 10. This being said, please make sure you run tests where the reset
> is performed during capture in the middle of a frame, to see if it changes the
> number of iterations.
>

The only way I can think to do this is to stream_on then immediately
stream_off before we get the frame and thus casue the interface reset.
Any other idea?

[snip]

> > >>>> +
> > >>>> +/**
> > >>>> + * 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 capture.
> > >>>> + */
> > >>>> +static int ceu_capture(struct ceu_device *ceudev)
> > >>>> +{
> > >>>> +	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> > >>>> +	dma_addr_t phys_addr_top;
> > >>>> +	u32 status;
> > >>>> +
> > >>>> +	/* Clean interrupt status and re-enable interrupts */
> > >>>> +	status = ceu_read(ceudev, CEU_CETCR);
> > >>>> +	ceu_write(ceudev, CEU_CEIER,
> > >>>> +		  ceu_read(ceudev, CEU_CEIER) & ~CEU_CEIER_MASK);
> > >>>> +	ceu_write(ceudev, CEU_CETCR, ~status & CEU_CETCR_MAGIC);
> > >>>> +	ceu_write(ceudev, CEU_CEIER, CEU_CEIER_MASK);
> > >>>
> > >>> I wonder why there's a need to disable and reenable interrupts here.
> > >>
> > >> The original driver clearly said "The hardware is -very- picky about
> > >> this sequence" and I got scared and nerver touched this.
> > >
> > > How about experimenting to see how the hardware reacts ?
> >
> > Turns out this was not needed at all, both on RZ and SH4. I captured
> > several images without any issues in both platforms just clearing the
> > interrupt state without disabling interrutps.
>
> I wonder whether it could cause an issue when interrupts are raised by the
> hardware at the same time they are cleared by the driver. That's hard to test
> though.
>
> What happens when an interrupt source is masked by the CEIER register, is it
> still reported by the status CETCR register (obviously without raising an
> interrupt to the CPU), or does it not get flagged at all ?

They get flagged, yes, and right now I'm clearing all of them at the
beginning of IRQ handler writing ~CEU_CETR_ALL_INT to CETCR.

>
> > >> Also, I very much dislike the CEU_CETRC_MAGIC mask, but again the old
> > >> driver said "Acknoledge magical interrupt sources" and I was afraid to
> > >> change it (I can rename it though, to something lioke CEU_CETCR_ALL_INT
> > >> because that's what it is, a mask with all available interrupt source
> > >> enabled).
> > >
> > > I think renaming it is a good idea. Additionally, regardless of whether
> > > there is any hidden interrupt source, the datasheet mentions for all
> > > reserved bits that "The write  value  should always  be 0". They should
> > > read as 0, but masking them would be an additional safeguard.
> >
> > The HW manual is a bit confused (and confusing) on this point.
> > Yes, there is the statement you have cited here, but there's also
> > "to clear only the CPE bit to 0, write H'FFFF FFFE to CETCR" a few
> > lines above, which clearly contradicts the "write 0 to reserved bits"
> > thing.
> >
> > In practice, I'm now writing to 0 only bits to be cleared, and thus
> > writing 1s to everything else, reserved included. I haven't seen any
> > issue both on RZ and SH4 platforms.

And this is the above "wirting ~CEU_CETR_ALL_INT" to CETCR" I
mentioned above.

> >
> > > Also not that on the RZ/A1 platform bit 22 is documented as reserved, so
> > > you might want to compute the mask based on the CEU model.
> >
> > While I can use the .data pointer of 'of_device_id' for OF based
> > devices (RZ) to store the opportune IRQ mask, I'm not sure how to
> > do that for platform devices. Can I assume (platform data == SH) in
> > you opinion?
>
> Yes you can.

Awesome!

Thanks
   j

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

* Re: [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver
  2017-12-22 14:40             ` jacopo mondi
@ 2017-12-23 11:39               ` Laurent Pinchart
  0 siblings, 0 replies; 56+ messages in thread
From: Laurent Pinchart @ 2017-12-23 11:39 UTC (permalink / raw)
  To: jacopo mondi
  Cc: Jacopo Mondi, magnus.damm, geert, mchehab, hverkuil,
	linux-renesas-soc, linux-media, linux-sh, linux-kernel

Hi Jacopo,

On Friday, 22 December 2017 16:40:16 EET jacopo mondi wrote:
> On Fri, Dec 22, 2017 at 02:03:41PM +0200, Laurent Pinchart wrote:
> > On Thursday, 21 December 2017 18:27:02 EET jacopo mondi wrote:
> >> On Mon, Dec 18, 2017 at 05:28:43PM +0200, Laurent Pinchart wrote:
> >>> On Monday, 18 December 2017 14:25:12 EET jacopo mondi wrote:
> >>>> On Mon, Dec 11, 2017 at 06:15:23PM +0200, Laurent Pinchart wrote:
> >> 
> >> [snip]
> >> 
> >>>>>> +/**
> >>>>>> + * ceu_soft_reset() - Software reset the CEU interface
> >>>>>> + */
> >>>>>> +static int ceu_soft_reset(struct ceu_device *ceudev)
> >>>>>> +{
> >>>>>> +	unsigned int reset_done;
> >>>>>> +	unsigned int i;
> >>>>>> +
> >>>>>> +	ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CPKIL);
> >>>>>> +
> >>>>>> +	reset_done = 0;
> >>>>>> +	for (i = 0; i < 1000 && !reset_done; i++) {
> >>>>>> +		udelay(1);
> >>>>>> +		if (!(ceu_read(ceudev, CEU_CSTSR) & CEU_CSTRST_CPTON))
> >>>>>> +			reset_done++;
> >>>>>> +	}
> >>>>> 
> >>>>> How many iterations does this typically require ? Wouldn't a sleep
> >>>>> be better than a delay ? As far as I can tell the ceu_soft_reset()
> >>>>> function is only called with interrupts disabled (in interrupt
> >>>>> context) from ceu_capture() in an error path, and that code should be
> >>>>> reworked to make it possible to sleep if a reset takes too long.
> >>>> 
> >>>> The HW manual does not provide any indication about absolute timings.
> >>>> I can empirically try and see, but that would just be a hint.
> >>> 
> >>> That's why I asked how many iterations it typically takes :-) A hint
> >>> is enough to start with, preferably on both SH and ARM SoCs.
> >> 
> >> I've seen only 0s when printing out how many cycles it takes to clear
> >> both registers. This means 1usec is enough, therefore I would keep using
> >> udelay here. Also, I would reduce the attempts to 100 here (or even
> >> 10), as if a single one is typically enough, 1000 is definitely an
> >> overkill.
> > 
> > I'd go for 10. This being said, please make sure you run tests where the
> > reset is performed during capture in the middle of a frame, to see if it
> > changes the number of iterations.
> 
> The only way I can think to do this is to stream_on then immediately
> stream_off before we get the frame and thus casue the interface reset.
> Any other idea?

I think we should test reset of the state machine both during vertical 
blanking when it's waiting for a frame, and in the middle of the frame. The 
former should be easy to test by stopping the stream immediately before the 
sensor starts outputting a frame (if you can disable the HSYNC and/or VSYNC 
outputs of the sensor it would ensure that the CEU doesn't start receiving 
data, that could be useful). The latter shouldn't be difficult to test with an 
appropriate delay from the frame end interrupt to the stream stop.

> [snip]
> 
> >>>>>> +
> >>>>>> +/**
> >>>>>> + * 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 capture.
> >>>>>> + */
> >>>>>> +static int ceu_capture(struct ceu_device *ceudev)
> >>>>>> +{
> >>>>>> +	struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix;
> >>>>>> +	dma_addr_t phys_addr_top;
> >>>>>> +	u32 status;
> >>>>>> +
> >>>>>> +	/* Clean interrupt status and re-enable interrupts */
> >>>>>> +	status = ceu_read(ceudev, CEU_CETCR);
> >>>>>> +	ceu_write(ceudev, CEU_CEIER,
> >>>>>> +		  ceu_read(ceudev, CEU_CEIER) & ~CEU_CEIER_MASK);
> >>>>>> +	ceu_write(ceudev, CEU_CETCR, ~status & CEU_CETCR_MAGIC);
> >>>>>> +	ceu_write(ceudev, CEU_CEIER, CEU_CEIER_MASK);
> >>>>> 
> >>>>> I wonder why there's a need to disable and reenable interrupts here.
> >>>> 
> >>>> The original driver clearly said "The hardware is -very- picky about
> >>>> this sequence" and I got scared and nerver touched this.
> >>> 
> >>> How about experimenting to see how the hardware reacts ?
> >> 
> >> Turns out this was not needed at all, both on RZ and SH4. I captured
> >> several images without any issues in both platforms just clearing the
> >> interrupt state without disabling interrutps.
> > 
> > I wonder whether it could cause an issue when interrupts are raised by the
> > hardware at the same time they are cleared by the driver. That's hard to
> > test though.
> > 
> > What happens when an interrupt source is masked by the CEIER register, is
> > it still reported by the status CETCR register (obviously without raising
> > an interrupt to the CPU), or does it not get flagged at all ?
> 
> They get flagged, yes, and right now I'm clearing all of them at the
> beginning of IRQ handler writing ~CEU_CETR_ALL_INT to CETCR.

OK. If they didn't get flagged it would mean that disabling interrupts while 
clearing the flags could have an influence on what interrupts are flagged. As 
they get flagged when disabled it should make no difference, so I'm not 
concerned with this change. You might however want to keep a local patch in 
one of your trees that disable interrupts to clear the status register, just 
to remember that it could be a potential issue if we later receive a bug 
report that could be related to interrupt handling. Or your memory is better 
than mine and there's no need for a reminder :-)

> >>> Also, I very much dislike the CEU_CETRC_MAGIC mask, but again the old
> >>>> driver said "Acknoledge magical interrupt sources" and I was afraid
> >>>> to change it (I can rename it though, to something lioke
> >>>> CEU_CETCR_ALL_INT because that's what it is, a mask with all available
> >>>> interrupt source enabled).
> >>> 
> >>> I think renaming it is a good idea. Additionally, regardless of
> >>> whether there is any hidden interrupt source, the datasheet mentions for
> >>> all reserved bits that "The write  value  should always  be 0". They
> >>> should read as 0, but masking them would be an additional safeguard.
> >> 
> >> The HW manual is a bit confused (and confusing) on this point.
> >> Yes, there is the statement you have cited here, but there's also
> >> "to clear only the CPE bit to 0, write H'FFFF FFFE to CETCR" a few
> >> lines above, which clearly contradicts the "write 0 to reserved bits"
> >> thing.
> >> 
> >> In practice, I'm now writing to 0 only bits to be cleared, and thus
> >> writing 1s to everything else, reserved included. I haven't seen any
> >> issue both on RZ and SH4 platforms.
> 
> And this is the above "wirting ~CEU_CETR_ALL_INT" to CETCR" I
> mentioned above.
> 
> >>> Also not that on the RZ/A1 platform bit 22 is documented as reserved,
> >>> so you might want to compute the mask based on the CEU model.
> >> 
> >> While I can use the .data pointer of 'of_device_id' for OF based
> >> devices (RZ) to store the opportune IRQ mask, I'm not sure how to
> >> do that for platform devices. Can I assume (platform data == SH) in
> >> you opinion?
> > 
> > Yes you can.
> 
> Awesome!

-- 
Regards,

Laurent Pinchart

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

end of thread, other threads:[~2017-12-23 11:39 UTC | newest]

Thread overview: 56+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-11-15 10:55 [PATCH v1 00/10] Renesas Capture Engine Unit (CEU) V4L2 driver Jacopo Mondi
2017-11-15 10:55 ` [PATCH v1 01/10] dt-bindings: media: Add Renesas CEU bindings Jacopo Mondi
2017-11-15 11:32   ` Kieran Bingham
2017-11-15 12:33   ` Sakari Ailus
2017-12-11 14:24     ` Laurent Pinchart
2017-11-15 13:07   ` Geert Uytterhoeven
2017-11-15 18:15     ` jacopo mondi
2017-11-15 18:39       ` Geert Uytterhoeven
2017-11-15 10:55 ` [PATCH v1 02/10] include: media: Add Renesas CEU driver interface Jacopo Mondi
2017-11-15 12:36   ` Sakari Ailus
2017-12-11 14:26     ` Laurent Pinchart
2017-12-11 14:32       ` Laurent Pinchart
2017-11-15 10:55 ` [PATCH v1 03/10] v4l: platform: Add Renesas CEU driver Jacopo Mondi
2017-11-15 12:45   ` Sakari Ailus
2017-11-15 14:25     ` jacopo mondi
2017-11-17  0:36       ` Sakari Ailus
2017-11-17  9:33         ` jacopo mondi
2017-11-25 15:56           ` Sakari Ailus
2017-11-25 18:17             ` jacopo mondi
2017-12-11 15:04         ` Laurent Pinchart
2017-12-11 16:15   ` Laurent Pinchart
2017-12-18 12:25     ` jacopo mondi
2017-12-18 15:28       ` Laurent Pinchart
2017-12-21 16:27         ` jacopo mondi
2017-12-22 12:03           ` Laurent Pinchart
2017-12-22 14:40             ` jacopo mondi
2017-12-23 11:39               ` Laurent Pinchart
2017-12-19 11:57     ` jacopo mondi
2017-12-19 13:07       ` Laurent Pinchart
2017-12-19 13:28         ` Sakari Ailus
2017-12-19 13:52           ` Laurent Pinchart
2017-12-13 12:03   ` Hans Verkuil
2017-12-18 14:12     ` jacopo mondi
2017-11-15 10:55 ` [PATCH v1 04/10] ARM: dts: r7s72100: Add Capture Engine Unit (CEU) Jacopo Mondi
2017-11-17 14:22   ` Simon Horman
2017-11-23  9:41   ` Geert Uytterhoeven
2017-11-15 10:55 ` [PATCH v1 05/10] arch: sh: migor: Use new renesas-ceu camera driver Jacopo Mondi
2017-12-11 14:36   ` Laurent Pinchart
2017-12-12 10:00   ` Laurent Pinchart
2017-11-15 10:55 ` [PATCH v1 06/10] sh: sh7722: Rename CEU clock Jacopo Mondi
2017-11-15 13:13   ` Geert Uytterhoeven
2017-11-17  9:15     ` jacopo mondi
2017-11-15 10:56 ` [PATCH v1 07/10] v4l: i2c: Copy ov772x soc_camera sensor driver Jacopo Mondi
2017-12-11 14:49   ` Laurent Pinchart
2017-12-13 12:10   ` Hans Verkuil
2017-12-13 13:02   ` Philippe Ombredanne
2017-11-15 10:56 ` [PATCH v1 08/10] media: i2c: ov772x: Remove soc_camera dependencies Jacopo Mondi
2017-11-17  0:43   ` Sakari Ailus
2017-11-17  9:14     ` jacopo mondi
2017-11-25 16:04       ` Sakari Ailus
2017-12-11 14:47   ` Laurent Pinchart
2017-11-15 10:56 ` [PATCH v1 09/10] v4l: i2c: Copy tw9910 soc_camera sensor driver Jacopo Mondi
2017-12-11 14:50   ` Laurent Pinchart
2017-11-15 10:56 ` [PATCH v1 10/10] media: i2c: tw9910: Remove soc_camera dependencies Jacopo Mondi
2017-12-11 14:55   ` Laurent Pinchart
2017-12-13 12:13   ` Hans Verkuil

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).