LKML Archive on lore.kernel.org
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 [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 [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 [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 [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 [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 [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 [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 [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 [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 [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).