2022-02-09 07:04:28

by Jean-Michel Hautbois

[permalink] [raw]
Subject: [PATCH v5 00/11] Add support for BCM2835 camera interface (unicam)

Hello !

This patch series adds the BCM2835 CCP2/CSI2 camera interface named
unicam. This driver is already used in the out-of-tree linux-rpi
repository [1], and this work aims to support it in mainline.

The series is based on top of:
- Rebased on top of 5.17-rc1 Tomi Valkeinen's multiplexed stream work, on
his multistream/work branch [2] which has been submitted as v10 at the
time of this writing. The objective is to demonstrate the use of
multiplexed streams on a real world widely used example as well as
supporting unicam mainline.
- The "Add 12bit and 14bit luma-only formats" series [3] is partly
applied (the Y10P format bug) the new formats are now part of this
series.

The series is made of 11 patches:
- 1/11 and 2/11 introduce the new formats needed for the unicam driver
- 3/11 introduces dt-bindings documentation
- 4/11 adds the driver support in media/platform
- 5/11 adds the MAINTAINERS entry
- 6/11 introduces the csi nodes in the bcm2711 file, in a disabled state
- 7/11 to 11/11 modifies imx219 driver to make it use the multiplexed
streams API

All those patches are in my tree [4].

Patch 5/12 comes from the linux-rpi work [1] with substantial
modifications:
- Removed the non-mc API which is deprecated, and not needed upstream
- Moved from one video node with two source pads (image and embedded) to
two video nodes
- Added a subdev between the sensor and the video nodes to properly
route the streams
- Added support for multiplexed streams API

In order to test it, one will need a RPi board and the camv2 (imx219)
sensor. An updated v4l-utils is also needed [5] to have support for
multiplexed streams.

v4l2-compliance passes on both video devices, without streaming testing
though with one exception: as the colorspace support is removed in v3,
we now have:

test VIDIOC_G_FMT: OK
fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc,
pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
test VIDIOC_TRY_FMT: FAIL
fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc,
pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
test VIDIOC_S_FMT: FAIL

This series since its v2 does integrate the device tree nodes into the
upstream BCM2835 or BCM2711 device tree support.

In order to properly configure the media pipeline, it is needed to call
the usual ioctls, and configure routing in order to send the embedded
data from the sensor to the "unicam-embedded" device node :

```
media=0
media-ctl -d${media} -l "'imx219 2-0010':0->'unicam-subdev':0 [1]"
media-ctl -d${media} -l "'unicam-subdev':1->'unicam-image':0 [1]"
media-ctl -d${media} -v -R "'unicam-subdev' [0/0->1/0[1],0/1->2/0[1]]"
media-ctl -d${media} -V "'imx219 2-0010':0/0 [fmt:SRGGB10_1X10/3280x2464 field:none]"
v4l2-ctl -d0 --set-fmt-video width=3280,height=2464,pixelformat='pRAA',field=none
media-ctl -d${media} -v -V "'imx219 2-0010':0/1 [fmt:METADATA_8/16384x1 field:none]"
media-ctl -d${media} -p
```

Be sure to configure the routes before setting the format, as s_routing
resets the default format.

The media-ctl topology is:
```
pi@raspberrypi:~ $ media-ctl -d${media} -p
Media controller API version 5.16.0

Media device information
------------------------
driver unicam
model unicam
serial
bus info platform:fe801000.csi
hw revision 0x0
driver version 5.16.0

Device topology
- entity 1: unicam-subdev (3 pads, 3 links, 2 routes)
type V4L2 subdev subtype Unknown flags 0
device node name /dev/v4l-subdev0
routes:
0/0 -> 1/0 [ACTIVE]
0/1 -> 2/0 [ACTIVE]
pad0: Sink
[stream:0 fmt:SRGGB10_1X10/3280x2464 field:none colorspace:raw]
[stream:1 fmt:METADATA_8/16384x1 field:none]
<- "imx219 2-0010":0 [ENABLED,IMMUTABLE]
pad1: Source
[stream:0 fmt:SRGGB10_1X10/3280x2464 field:none colorspace:raw]
-> "unicam-image":0 [ENABLED,IMMUTABLE]
pad2: Source
[stream:0 fmt:METADATA_8/16384x1 field:none]
-> "unicam-embedded":0 [ENABLED,IMMUTABLE]

- entity 5: imx219 2-0010 (1 pad, 1 link, 2 routes)
type V4L2 subdev subtype Sensor flags 0
device node name /dev/v4l-subdev1
routes:
0/0 -> 0/0 [ACTIVE, IMMUTABLE, SOURCE]
0/0 -> 0/1 [ACTIVE, SOURCE]
pad0: Source
[stream:0 fmt:SRGGB10_1X10/3280x2464 field:none colorspace:raw
crop.bounds:(8,8)/3280x2464
crop:(8,8)/3280x2464]
[stream:1 fmt:METADATA_8/16384x1 field:none
crop.bounds:(8,8)/3280x2464
crop:(8,8)/3280x2464]
-> "unicam-subdev":0 [ENABLED,IMMUTABLE]

- entity 9: unicam-image (1 pad, 1 link, 0 route)
type Node subtype V4L flags 1
device node name /dev/video0
pad0: Sink
<- "unicam-subdev":1 [ENABLED,IMMUTABLE]

- entity 15: unicam-embedded (1 pad, 1 link, 0 route)
type Node subtype V4L flags 0
device node name /dev/video1
pad0: Sink
<- "unicam-subdev":2 [ENABLED,IMMUTABLE]

```

Then a frame can be capture with yavta:
`yavta --capture=1 /dev/video0 -F/tmp/test-#.bin`

In v3, capture on the metadata node (/dev/video1 in my case) can't be
started if capture on image node (/dev/video0) is not already started.
This can be tested using yavta, letting it capture frames indefinitely
and start another yavta instance on the /dev/video1 node.

[1]: https://github.com/raspberrypi/linux/tree/rpi-5.15.y
[2]: https://git.kernel.org/pub/scm/linux/kernel/git/tomba/linux.git/log/?h=multistream/work
[3]: https://patchwork.linuxtv.org/project/linux-media/list/?series=7099
[4]: https://github.com/jhautbois/linux-rpi/tree/jmh/unicam-multiplexed-streams
[5]: https://github.com/jhautbois/v4l-utils/tree/jmh/tomi-multiplexed-streams

Jean-Michel Hautbois (11):
media: v4l: Add V4L2-PIX-FMT-Y12P format
media: v4l: Add V4L2-PIX-FMT-Y14P format
dt-bindings: media: Add bindings for bcm2835-unicam
media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface
media: MAINTAINERS: add bcm2835 unicam driver
ARM: dts: bcm2711: Add unicam CSI nodes
media: imx219: Rename mbus codes array
media: imx219: Switch from open to init_cfg
media: imx219: Introduce the set_routing operation
media: imx219: use a local v4l2_subdev to simplify reading
media: imx219: Add support for the V4L2 subdev active state

.../bindings/media/brcm,bcm2835-unicam.yaml | 117 +
.../media/v4l/pixfmt-yuv-luma.rst | 44 +
MAINTAINERS | 8 +
arch/arm/boot/dts/bcm2711-rpi.dtsi | 15 +
arch/arm/boot/dts/bcm2711.dtsi | 22 +
drivers/media/i2c/imx219.c | 324 ++-
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/bcm2835/Kconfig | 21 +
drivers/media/platform/bcm2835/Makefile | 3 +
.../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
.../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
drivers/media/v4l2-core/v4l2-ioctl.c | 2 +
include/uapi/linux/videodev2.h | 2 +
14 files changed, 3256 insertions(+), 128 deletions(-)
create mode 100644 Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml
create mode 100644 drivers/media/platform/bcm2835/Kconfig
create mode 100644 drivers/media/platform/bcm2835/Makefile
create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c

--
2.32.0



2022-02-09 07:07:28

by Jean-Michel Hautbois

[permalink] [raw]
Subject: [PATCH v5 02/11] media: v4l: Add V4L2-PIX-FMT-Y14P format

This is a packed grey-scale image format with a depth of 14 bits per
pixel. Every four consecutive samples are packed into seven bytes. Each
of the first four bytes contain the eight high order bits of the pixels,
and the three following bytes contains the six least significants bits
of each pixel, in the same order.

As the other formats only needed 5 bytes before, append two bytes in the
documentation array.

Signed-off-by: Jean-Michel Hautbois <[email protected]>
Reviewed-by: Laurent Pinchart <[email protected]>
---
.../media/v4l/pixfmt-yuv-luma.rst | 33 +++++++++++++++++++
drivers/media/v4l2-core/v4l2-ioctl.c | 1 +
include/uapi/linux/videodev2.h | 1 +
3 files changed, 35 insertions(+)

diff --git a/Documentation/userspace-api/media/v4l/pixfmt-yuv-luma.rst b/Documentation/userspace-api/media/v4l/pixfmt-yuv-luma.rst
index d37ce6027095..c64485b03d27 100644
--- a/Documentation/userspace-api/media/v4l/pixfmt-yuv-luma.rst
+++ b/Documentation/userspace-api/media/v4l/pixfmt-yuv-luma.rst
@@ -36,6 +36,8 @@ are often referred to as greyscale formats.
- Byte 2
- Byte 3
- Byte 4
+ - Byte 5
+ - Byte 6

* .. _V4L2-PIX-FMT-GREY:

@@ -47,6 +49,8 @@ are often referred to as greyscale formats.
- ...
- ...
- ...
+ - ...
+ - ...

* .. _V4L2-PIX-FMT-Y10:

@@ -58,6 +62,8 @@ are often referred to as greyscale formats.
- ...
- ...
- ...
+ - ...
+ - ...

* .. _V4L2-PIX-FMT-Y10BPACK:

@@ -69,6 +75,8 @@ are often referred to as greyscale formats.
- Y'\ :sub:`1`\ [3:0] Y'\ :sub:`2`\ [9:6]
- Y'\ :sub:`2`\ [5:0] Y'\ :sub:`3`\ [9:8]
- Y'\ :sub:`3`\ [7:0]
+ - ...
+ - ...

* .. _V4L2-PIX-FMT-Y10P:

@@ -80,6 +88,8 @@ are often referred to as greyscale formats.
- Y'\ :sub:`2`\ [9:2]
- Y'\ :sub:`3`\ [9:2]
- Y'\ :sub:`3`\ [1:0] Y'\ :sub:`2`\ [1:0] Y'\ :sub:`1`\ [1:0] Y'\ :sub:`0`\ [1:0]
+ - ...
+ - ...

* .. _V4L2-PIX-FMT-Y12:

@@ -91,6 +101,8 @@ are often referred to as greyscale formats.
- ...
- ...
- ...
+ - ...
+ - ...

* .. _V4L2-PIX-FMT-Y12P:

@@ -102,6 +114,8 @@ are often referred to as greyscale formats.
- Y'\ :sub:`1`\ [3:0] Y'\ :sub:`0`\ [3:0]
- ...
- ...
+ - ...
+ - ...

* .. _V4L2-PIX-FMT-Y14:

@@ -113,6 +127,21 @@ are often referred to as greyscale formats.
- ...
- ...
- ...
+ - ...
+ - ...
+
+ * .. _V4L2-PIX-FMT-Y14P:
+
+ - ``V4L2_PIX_FMT_Y14P``
+ - 'Y14P'
+
+ - Y'\ :sub:`0`\ [13:6]
+ - Y'\ :sub:`1`\ [13:6]
+ - Y'\ :sub:`2`\ [13:6]
+ - Y'\ :sub:`3`\ [13:6]
+ - Y'\ :sub:`1`\ [1:0] Y'\ :sub:`0`\ [5:0]
+ - Y'\ :sub:`2`\ [3:0] Y'\ :sub:`1`\ [5:2]
+ - Y'\ :sub:`3`\ [5:0] Y'\ :sub:`2`\ [5:4]

* .. _V4L2-PIX-FMT-Y16:

@@ -124,6 +153,8 @@ are often referred to as greyscale formats.
- ...
- ...
- ...
+ - ...
+ - ...

* .. _V4L2-PIX-FMT-Y16-BE:

@@ -135,6 +166,8 @@ are often referred to as greyscale formats.
- ...
- ...
- ...
+ - ...
+ - ...

.. raw:: latex

diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
index 12862e0803bf..c321cbd9de54 100644
--- a/drivers/media/v4l2-core/v4l2-ioctl.c
+++ b/drivers/media/v4l2-core/v4l2-ioctl.c
@@ -1267,6 +1267,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
case V4L2_PIX_FMT_Y10BPACK: descr = "10-bit Greyscale (Packed)"; break;
case V4L2_PIX_FMT_Y10P: descr = "10-bit Greyscale (MIPI Packed)"; break;
case V4L2_PIX_FMT_Y12P: descr = "12-bit Greyscale (MIPI Packed)"; break;
+ case V4L2_PIX_FMT_Y14P: descr = "14-bit Greyscale (MIPI Packed)"; break;
case V4L2_PIX_FMT_Y8I: descr = "Interleaved 8-bit Greyscale"; break;
case V4L2_PIX_FMT_Y12I: descr = "Interleaved 12-bit Greyscale"; break;
case V4L2_PIX_FMT_Z16: descr = "16-bit Depth"; break;
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index bef488205fea..18611289217f 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -570,6 +570,7 @@ struct v4l2_pix_format {
#define V4L2_PIX_FMT_Y10BPACK v4l2_fourcc('Y', '1', '0', 'B') /* 10 Greyscale bit-packed */
#define V4L2_PIX_FMT_Y10P v4l2_fourcc('Y', '1', '0', 'P') /* 10 Greyscale, MIPI RAW10 packed */
#define V4L2_PIX_FMT_Y12P v4l2_fourcc('Y', '1', '2', 'P') /* 12 Greyscale, MIPI RAW12 packed */
+#define V4L2_PIX_FMT_Y14P v4l2_fourcc('Y', '1', '4', 'P') /* 14 Greyscale, MIPI RAW14 packed */

/* Palette formats */
#define V4L2_PIX_FMT_PAL8 v4l2_fourcc('P', 'A', 'L', '8') /* 8 8-bit palette */
--
2.32.0


2022-02-09 07:16:02

by Jean-Michel Hautbois

[permalink] [raw]
Subject: [PATCH v5 07/11] media: imx219: Rename mbus codes array

The imx219 is using the name codes[] for the mbus format which is not
easy to read and know what it means. Change it to imx219_mbus_formats.

Signed-off-by: Jean-Michel Hautbois <[email protected]>
Reviewed-by: Laurent Pinchart <[email protected]>
---
drivers/media/i2c/imx219.c | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c
index e10af3f74b38..74dba5e61201 100644
--- a/drivers/media/i2c/imx219.c
+++ b/drivers/media/i2c/imx219.c
@@ -429,7 +429,7 @@ static const char * const imx219_supply_name[] = {
* - v flip
* - h&v flips
*/
-static const u32 codes[] = {
+static const u32 imx219_mbus_formats[] = {
MEDIA_BUS_FMT_SRGGB10_1X10,
MEDIA_BUS_FMT_SGRBG10_1X10,
MEDIA_BUS_FMT_SGBRG10_1X10,
@@ -655,17 +655,17 @@ static u32 imx219_get_format_code(struct imx219 *imx219, u32 code)

lockdep_assert_held(&imx219->mutex);

- for (i = 0; i < ARRAY_SIZE(codes); i++)
- if (codes[i] == code)
+ for (i = 0; i < ARRAY_SIZE(imx219_mbus_formats); i++)
+ if (imx219_mbus_formats[i] == code)
break;

- if (i >= ARRAY_SIZE(codes))
+ if (i >= ARRAY_SIZE(imx219_mbus_formats))
i = 0;

i = (i & ~3) | (imx219->vflip->val ? 2 : 0) |
(imx219->hflip->val ? 1 : 0);

- return codes[i];
+ return imx219_mbus_formats[i];
}

static void imx219_set_default_format(struct imx219 *imx219)
@@ -808,11 +808,11 @@ static int imx219_enum_mbus_code(struct v4l2_subdev *sd,
{
struct imx219 *imx219 = to_imx219(sd);

- if (code->index >= (ARRAY_SIZE(codes) / 4))
+ if (code->index >= (ARRAY_SIZE(imx219_mbus_formats) / 4))
return -EINVAL;

mutex_lock(&imx219->mutex);
- code->code = imx219_get_format_code(imx219, codes[code->index * 4]);
+ code->code = imx219_get_format_code(imx219, imx219_mbus_formats[code->index * 4]);
mutex_unlock(&imx219->mutex);

return 0;
@@ -908,14 +908,14 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd,

mutex_lock(&imx219->mutex);

- for (i = 0; i < ARRAY_SIZE(codes); i++)
- if (codes[i] == fmt->format.code)
+ for (i = 0; i < ARRAY_SIZE(imx219_mbus_formats); i++)
+ if (imx219_mbus_formats[i] == fmt->format.code)
break;
- if (i >= ARRAY_SIZE(codes))
+ if (i >= ARRAY_SIZE(imx219_mbus_formats))
i = 0;

/* Bayer order varies with flips */
- fmt->format.code = imx219_get_format_code(imx219, codes[i]);
+ fmt->format.code = imx219_get_format_code(imx219, imx219_mbus_formats[i]);

mode = v4l2_find_nearest_size(supported_modes,
ARRAY_SIZE(supported_modes),
--
2.32.0


2022-02-09 07:37:34

by Jean-Michel Hautbois

[permalink] [raw]
Subject: [PATCH v5 10/11] media: imx219: use a local v4l2_subdev to simplify reading

There is no need to dereference the imx219 structure. Use a local
v4l2_subdev instead.

Signed-off-by: Jean-Michel Hautbois <[email protected]>
Reviewed-by: Laurent Pinchart <[email protected]>
---
drivers/media/i2c/imx219.c | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c
index 35b61fad8e35..f005ad8d2124 100644
--- a/drivers/media/i2c/imx219.c
+++ b/drivers/media/i2c/imx219.c
@@ -1509,6 +1509,7 @@ static int imx219_check_hwcfg(struct device *dev)
static int imx219_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
+ struct v4l2_subdev *sd;
struct imx219 *imx219;
int ret;

@@ -1516,7 +1517,8 @@ static int imx219_probe(struct i2c_client *client)
if (!imx219)
return -ENOMEM;

- v4l2_i2c_subdev_init(&imx219->sd, client, &imx219_subdev_ops);
+ sd = &imx219->sd;
+ v4l2_i2c_subdev_init(sd, client, &imx219_subdev_ops);

/* Check the hardware configuration in device tree */
if (imx219_check_hwcfg(dev))
@@ -1583,10 +1585,10 @@ static int imx219_probe(struct i2c_client *client)
goto error_power_off;

/* Initialize subdev */
- imx219->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
- V4L2_SUBDEV_FL_HAS_EVENTS |
- V4L2_SUBDEV_FL_MULTIPLEXED;
- imx219->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+ V4L2_SUBDEV_FL_HAS_EVENTS |
+ V4L2_SUBDEV_FL_MULTIPLEXED;
+ sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;

/* Initialize source pad */
imx219->pad.flags = MEDIA_PAD_FL_SOURCE;
@@ -1594,13 +1596,13 @@ static int imx219_probe(struct i2c_client *client)
/* Initialize default format */
imx219_set_default_format(imx219);

- ret = media_entity_pads_init(&imx219->sd.entity, 1, &imx219->pad);
+ ret = media_entity_pads_init(&sd->entity, 1, &imx219->pad);
if (ret) {
dev_err(dev, "failed to init entity pads: %d\n", ret);
goto error_handler_free;
}

- ret = v4l2_async_register_subdev_sensor(&imx219->sd);
+ ret = v4l2_async_register_subdev_sensor(sd);
if (ret < 0) {
dev_err(dev, "failed to register sensor sub-device: %d\n", ret);
goto error_media_entity;
@@ -1614,7 +1616,7 @@ static int imx219_probe(struct i2c_client *client)
return 0;

error_media_entity:
- media_entity_cleanup(&imx219->sd.entity);
+ media_entity_cleanup(&sd->entity);

error_handler_free:
imx219_free_controls(imx219);
--
2.32.0


2022-02-09 08:07:00

by Jean-Michel Hautbois

[permalink] [raw]
Subject: [PATCH v5 08/11] media: imx219: Switch from open to init_cfg

Use the init_cfg pad level operation instead of the internal subdev
open operation to set default formats on the pads.
While at it, make the imx219_pad_ops more easier to read.

Signed-off-by: Jean-Michel Hautbois <[email protected]>
---
drivers/media/i2c/imx219.c | 63 +++++++++++++++-----------------------
1 file changed, 25 insertions(+), 38 deletions(-)

diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c
index 74dba5e61201..abcaee15c4a0 100644
--- a/drivers/media/i2c/imx219.c
+++ b/drivers/media/i2c/imx219.c
@@ -685,34 +685,6 @@ static void imx219_set_default_format(struct imx219 *imx219)
fmt->field = V4L2_FIELD_NONE;
}

-static int imx219_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
-{
- struct imx219 *imx219 = to_imx219(sd);
- struct v4l2_mbus_framefmt *try_fmt =
- v4l2_subdev_get_try_format(sd, fh->state, 0);
- struct v4l2_rect *try_crop;
-
- mutex_lock(&imx219->mutex);
-
- /* Initialize try_fmt */
- try_fmt->width = supported_modes[0].width;
- try_fmt->height = supported_modes[0].height;
- try_fmt->code = imx219_get_format_code(imx219,
- MEDIA_BUS_FMT_SRGGB10_1X10);
- try_fmt->field = V4L2_FIELD_NONE;
-
- /* Initialize try_crop rectangle. */
- try_crop = v4l2_subdev_get_try_crop(sd, fh->state, 0);
- try_crop->top = IMX219_PIXEL_ARRAY_TOP;
- try_crop->left = IMX219_PIXEL_ARRAY_LEFT;
- try_crop->width = IMX219_PIXEL_ARRAY_WIDTH;
- try_crop->height = IMX219_PIXEL_ARRAY_HEIGHT;
-
- mutex_unlock(&imx219->mutex);
-
- return 0;
-}
-
static int imx219_set_ctrl(struct v4l2_ctrl *ctrl)
{
struct imx219 *imx219 =
@@ -802,6 +774,25 @@ static const struct v4l2_ctrl_ops imx219_ctrl_ops = {
.s_ctrl = imx219_set_ctrl,
};

+static void imx219_init_formats(struct v4l2_subdev_state *state)
+{
+ struct v4l2_mbus_framefmt *format;
+
+ format = v4l2_state_get_stream_format(state, 0, 0);
+ format->code = imx219_mbus_formats[0];
+ format->width = supported_modes[0].width;
+ format->height = supported_modes[0].height;
+ format->field = V4L2_FIELD_NONE;
+ format->colorspace = V4L2_COLORSPACE_RAW;
+}
+
+static int imx219_init_cfg(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state)
+{
+ imx219_init_formats(state);
+ return 0;
+}
+
static int imx219_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code)
@@ -1255,11 +1246,12 @@ static const struct v4l2_subdev_video_ops imx219_video_ops = {
};

static const struct v4l2_subdev_pad_ops imx219_pad_ops = {
- .enum_mbus_code = imx219_enum_mbus_code,
- .get_fmt = imx219_get_pad_format,
- .set_fmt = imx219_set_pad_format,
- .get_selection = imx219_get_selection,
- .enum_frame_size = imx219_enum_frame_size,
+ .init_cfg = imx219_init_cfg,
+ .enum_mbus_code = imx219_enum_mbus_code,
+ .get_fmt = imx219_get_pad_format,
+ .set_fmt = imx219_set_pad_format,
+ .get_selection = imx219_get_selection,
+ .enum_frame_size = imx219_enum_frame_size,
};

static const struct v4l2_subdev_ops imx219_subdev_ops = {
@@ -1268,10 +1260,6 @@ static const struct v4l2_subdev_ops imx219_subdev_ops = {
.pad = &imx219_pad_ops,
};

-static const struct v4l2_subdev_internal_ops imx219_internal_ops = {
- .open = imx219_open,
-};
-
/* Initialize control handlers */
static int imx219_init_controls(struct imx219 *imx219)
{
@@ -1520,7 +1508,6 @@ static int imx219_probe(struct i2c_client *client)
goto error_power_off;

/* Initialize subdev */
- imx219->sd.internal_ops = &imx219_internal_ops;
imx219->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
V4L2_SUBDEV_FL_HAS_EVENTS;
imx219->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
--
2.32.0


2022-02-09 09:58:35

by Jean-Michel Hautbois

[permalink] [raw]
Subject: [PATCH v5 11/11] media: imx219: Add support for the V4L2 subdev active state

Now that we have multiplexed streams support in the V4L2 API, add
support for the subdev active state in the sensor.

In order to use state to manage the active configuration, we need to
initialize the subdevice state with a call to
v4l2_subdev_init_finalize() before registering it. The call to
v4l2_subdev_cleanup() is also required to release the resources before
unregistering.

Accessing the configuration is then done after a call to
v4l2_subdev_lock_state() and the set_fmt operation can then call
v4l2_state_get_stream_format() directly.

The get_fmt operation does not need to be complex, and a simple call to
v4l2_subdev_get_fmt will do the trick, as it will grab the configured
format based on the state.

Signed-off-by: Jean-Michel Hautbois <[email protected]>
---
drivers/media/i2c/imx219.c | 151 +++++++++++++++++++------------------
1 file changed, 77 insertions(+), 74 deletions(-)

diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c
index f005ad8d2124..f6322f39c3f1 100644
--- a/drivers/media/i2c/imx219.c
+++ b/drivers/media/i2c/imx219.c
@@ -887,78 +887,43 @@ static int imx219_enum_frame_size(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_frame_size_enum *fse)
{
- struct imx219 *imx219 = to_imx219(sd);
- u32 code;
-
- if (fse->index >= ARRAY_SIZE(supported_modes))
- return -EINVAL;
-
- mutex_lock(&imx219->mutex);
- code = imx219_get_format_code(imx219, fse->code);
- mutex_unlock(&imx219->mutex);
- if (fse->code != code)
- return -EINVAL;
-
- fse->min_width = supported_modes[fse->index].width;
- fse->max_width = fse->min_width;
- fse->min_height = supported_modes[fse->index].height;
- fse->max_height = fse->min_height;
+ unsigned int i;

- return 0;
-}
+ if (fse->stream == 0) {
+ for (i = 0; i < ARRAY_SIZE(imx219_mbus_formats); ++i) {
+ if (imx219_mbus_formats[i] == fse->code)
+ break;
+ }

-static void imx219_reset_colorspace(struct v4l2_mbus_framefmt *fmt)
-{
- fmt->colorspace = V4L2_COLORSPACE_SRGB;
- fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
- fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
- fmt->colorspace,
- fmt->ycbcr_enc);
- fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
-}
+ if (i == ARRAY_SIZE(imx219_mbus_formats))
+ return -EINVAL;

-static void imx219_update_pad_format(struct imx219 *imx219,
- const struct imx219_mode *mode,
- struct v4l2_subdev_format *fmt)
-{
- fmt->format.width = mode->width;
- fmt->format.height = mode->height;
- fmt->format.field = V4L2_FIELD_NONE;
- imx219_reset_colorspace(&fmt->format);
-}
+ if (fse->index >= ARRAY_SIZE(supported_modes))
+ return -EINVAL;

-static int __imx219_get_pad_format(struct imx219 *imx219,
- struct v4l2_subdev_state *sd_state,
- struct v4l2_subdev_format *fmt)
-{
- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
- struct v4l2_mbus_framefmt *try_fmt =
- v4l2_subdev_get_try_format(&imx219->sd, sd_state,
- fmt->pad);
- /* update the code which could change due to vflip or hflip: */
- try_fmt->code = imx219_get_format_code(imx219, try_fmt->code);
- fmt->format = *try_fmt;
+ fse->min_width = supported_modes[fse->index].width;
+ fse->max_width = fse->min_width;
+ fse->max_height = supported_modes[fse->index].height;
+ fse->min_height = fse->max_height;
} else {
- imx219_update_pad_format(imx219, imx219->mode, fmt);
- fmt->format.code = imx219_get_format_code(imx219,
- imx219->fmt.code);
+ if (fse->code != MEDIA_BUS_FMT_METADATA_8)
+ return -EINVAL;
+
+ fse->min_width = IMX219_EMBEDDED_LINE_WIDTH;
+ fse->max_width = fse->min_width;
+ fse->min_height = IMX219_NUM_EMBEDDED_LINES;
+ fse->max_height = fse->min_height;
}

return 0;
}

-static int imx219_get_pad_format(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *sd_state,
- struct v4l2_subdev_format *fmt)
+static void imx219_update_metadata_pad_format(struct v4l2_subdev_format *fmt)
{
- struct imx219 *imx219 = to_imx219(sd);
- int ret;
-
- mutex_lock(&imx219->mutex);
- ret = __imx219_get_pad_format(imx219, sd_state, fmt);
- mutex_unlock(&imx219->mutex);
-
- return ret;
+ fmt->format.width = IMX219_EMBEDDED_LINE_WIDTH;
+ fmt->format.height = IMX219_NUM_EMBEDDED_LINES;
+ fmt->format.code = MEDIA_BUS_FMT_METADATA_8;
+ fmt->format.field = V4L2_FIELD_NONE;
}

static int imx219_set_pad_format(struct v4l2_subdev *sd,
@@ -966,32 +931,58 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd,
struct v4l2_subdev_format *fmt)
{
struct imx219 *imx219 = to_imx219(sd);
+ struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd);
const struct imx219_mode *mode;
- struct v4l2_mbus_framefmt *framefmt;
+ struct v4l2_mbus_framefmt *format;
int exposure_max, exposure_def, hblank;
unsigned int i;
+ int ret = 0;

mutex_lock(&imx219->mutex);

+ if (fmt->pad != 0) {
+ dev_err(&client->dev, "%s Could not get pad %d\n", __func__,
+ fmt->pad);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (fmt->stream == 1) {
+ /* Only one embedded data mode is supported */
+ imx219_update_metadata_pad_format(fmt);
+ ret = 0;
+ goto done;
+ }
+
for (i = 0; i < ARRAY_SIZE(imx219_mbus_formats); i++)
if (imx219_mbus_formats[i] == fmt->format.code)
break;
if (i >= ARRAY_SIZE(imx219_mbus_formats))
i = 0;

- /* Bayer order varies with flips */
- fmt->format.code = imx219_get_format_code(imx219, imx219_mbus_formats[i]);
-
mode = v4l2_find_nearest_size(supported_modes,
ARRAY_SIZE(supported_modes),
width, height,
fmt->format.width, fmt->format.height);
- imx219_update_pad_format(imx219, mode, fmt);
- if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
- framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
- *framefmt = fmt->format;
- } else if (imx219->mode != mode ||
- imx219->fmt.code != fmt->format.code) {
+
+ v4l2_subdev_lock_state(sd_state);
+
+ /* Update the stored format and return it. */
+ format = v4l2_state_get_stream_format(sd_state, fmt->pad, fmt->stream);
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE && imx219->streaming) {
+ ret = -EBUSY;
+ goto err_state;
+ }
+
+ /* Bayer order varies with flips */
+ format->code = imx219_get_format_code(imx219, imx219_mbus_formats[i]);
+ format->width = mode->width;
+ format->height = mode->height;
+ /* Bayer order varies with flips */
+ fmt->format = *format;
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
imx219->fmt = fmt->format;
imx219->mode = mode;
/* Update limits and set FPS to default */
@@ -1018,9 +1009,12 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd,
hblank);
}

+err_state:
+ v4l2_subdev_unlock_state(sd_state);
+done:
mutex_unlock(&imx219->mutex);

- return 0;
+ return ret;
}

static int imx219_set_framefmt(struct imx219 *imx219)
@@ -1322,7 +1316,7 @@ static const struct v4l2_subdev_video_ops imx219_video_ops = {
static const struct v4l2_subdev_pad_ops imx219_pad_ops = {
.init_cfg = imx219_init_cfg,
.enum_mbus_code = imx219_enum_mbus_code,
- .get_fmt = imx219_get_pad_format,
+ .get_fmt = v4l2_subdev_get_fmt,
.set_fmt = imx219_set_pad_format,
.get_selection = imx219_get_selection,
.set_routing = imx219_set_routing,
@@ -1602,10 +1596,16 @@ static int imx219_probe(struct i2c_client *client)
goto error_handler_free;
}

+ ret = v4l2_subdev_init_finalize(sd);
+ if (ret) {
+ dev_err(dev, "failed to finalize sensor init: %d\n", ret);
+ goto error_media_entity;
+ }
+
ret = v4l2_async_register_subdev_sensor(sd);
if (ret < 0) {
dev_err(dev, "failed to register sensor sub-device: %d\n", ret);
- goto error_media_entity;
+ goto error_free_state;
}

/* Enable runtime PM and turn off the device */
@@ -1615,6 +1615,8 @@ static int imx219_probe(struct i2c_client *client)

return 0;

+error_free_state:
+ v4l2_subdev_cleanup(sd);
error_media_entity:
media_entity_cleanup(&sd->entity);

@@ -1633,6 +1635,7 @@ static int imx219_remove(struct i2c_client *client)
struct imx219 *imx219 = to_imx219(sd);

v4l2_async_unregister_subdev(sd);
+ v4l2_subdev_cleanup(sd);
media_entity_cleanup(&sd->entity);
imx219_free_controls(imx219);

--
2.32.0


2022-02-09 10:00:31

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v5 08/11] media: imx219: Switch from open to init_cfg

Hi Jean-Michel,

Thank you for the patch.

On Tue, Feb 08, 2022 at 04:50:24PM +0100, Jean-Michel Hautbois wrote:
> Use the init_cfg pad level operation instead of the internal subdev
> open operation to set default formats on the pads.
> While at it, make the imx219_pad_ops more easier to read.

It could have been useful to explain here why there's a separate
imx219_init_formats() function, as reviewers could wonder. It's clear
when reviewing the next patches though.

> Signed-off-by: Jean-Michel Hautbois <[email protected]>
> ---
> drivers/media/i2c/imx219.c | 63 +++++++++++++++-----------------------
> 1 file changed, 25 insertions(+), 38 deletions(-)
>
> diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c
> index 74dba5e61201..abcaee15c4a0 100644
> --- a/drivers/media/i2c/imx219.c
> +++ b/drivers/media/i2c/imx219.c
> @@ -685,34 +685,6 @@ static void imx219_set_default_format(struct imx219 *imx219)
> fmt->field = V4L2_FIELD_NONE;
> }
>
> -static int imx219_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
> -{
> - struct imx219 *imx219 = to_imx219(sd);
> - struct v4l2_mbus_framefmt *try_fmt =
> - v4l2_subdev_get_try_format(sd, fh->state, 0);
> - struct v4l2_rect *try_crop;
> -
> - mutex_lock(&imx219->mutex);
> -
> - /* Initialize try_fmt */
> - try_fmt->width = supported_modes[0].width;
> - try_fmt->height = supported_modes[0].height;
> - try_fmt->code = imx219_get_format_code(imx219,
> - MEDIA_BUS_FMT_SRGGB10_1X10);
> - try_fmt->field = V4L2_FIELD_NONE;
> -
> - /* Initialize try_crop rectangle. */
> - try_crop = v4l2_subdev_get_try_crop(sd, fh->state, 0);
> - try_crop->top = IMX219_PIXEL_ARRAY_TOP;
> - try_crop->left = IMX219_PIXEL_ARRAY_LEFT;
> - try_crop->width = IMX219_PIXEL_ARRAY_WIDTH;
> - try_crop->height = IMX219_PIXEL_ARRAY_HEIGHT;

The crop rectangle initialization is removed here, but not added below.

> -
> - mutex_unlock(&imx219->mutex);
> -
> - return 0;
> -}
> -
> static int imx219_set_ctrl(struct v4l2_ctrl *ctrl)
> {
> struct imx219 *imx219 =
> @@ -802,6 +774,25 @@ static const struct v4l2_ctrl_ops imx219_ctrl_ops = {
> .s_ctrl = imx219_set_ctrl,
> };
>
> +static void imx219_init_formats(struct v4l2_subdev_state *state)
> +{
> + struct v4l2_mbus_framefmt *format;
> +
> + format = v4l2_state_get_stream_format(state, 0, 0);
> + format->code = imx219_mbus_formats[0];
> + format->width = supported_modes[0].width;
> + format->height = supported_modes[0].height;
> + format->field = V4L2_FIELD_NONE;
> + format->colorspace = V4L2_COLORSPACE_RAW;
> +}
> +
> +static int imx219_init_cfg(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state)
> +{
> + imx219_init_formats(state);
> + return 0;
> +}
> +
> static int imx219_enum_mbus_code(struct v4l2_subdev *sd,
> struct v4l2_subdev_state *sd_state,
> struct v4l2_subdev_mbus_code_enum *code)
> @@ -1255,11 +1246,12 @@ static const struct v4l2_subdev_video_ops imx219_video_ops = {
> };
>
> static const struct v4l2_subdev_pad_ops imx219_pad_ops = {
> - .enum_mbus_code = imx219_enum_mbus_code,
> - .get_fmt = imx219_get_pad_format,
> - .set_fmt = imx219_set_pad_format,
> - .get_selection = imx219_get_selection,
> - .enum_frame_size = imx219_enum_frame_size,
> + .init_cfg = imx219_init_cfg,
> + .enum_mbus_code = imx219_enum_mbus_code,
> + .get_fmt = imx219_get_pad_format,
> + .set_fmt = imx219_set_pad_format,
> + .get_selection = imx219_get_selection,
> + .enum_frame_size = imx219_enum_frame_size,
> };
>
> static const struct v4l2_subdev_ops imx219_subdev_ops = {
> @@ -1268,10 +1260,6 @@ static const struct v4l2_subdev_ops imx219_subdev_ops = {
> .pad = &imx219_pad_ops,
> };
>
> -static const struct v4l2_subdev_internal_ops imx219_internal_ops = {
> - .open = imx219_open,
> -};
> -
> /* Initialize control handlers */
> static int imx219_init_controls(struct imx219 *imx219)
> {
> @@ -1520,7 +1508,6 @@ static int imx219_probe(struct i2c_client *client)
> goto error_power_off;
>
> /* Initialize subdev */
> - imx219->sd.internal_ops = &imx219_internal_ops;
> imx219->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
> V4L2_SUBDEV_FL_HAS_EVENTS;
> imx219->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;

--
Regards,

Laurent Pinchart

2022-02-09 10:05:45

by Jean-Michel Hautbois

[permalink] [raw]
Subject: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Add driver for the Unicam camera receiver block on BCM283x processors.
It is represented as two video device nodes: unicam-image and
unicam-embedded which are connected to an internal subdev (named
unicam-subdev) in order to manage streams routing.

Signed-off-by: Dave Stevenson <[email protected]>
Signed-off-by: Naushir Patuck <[email protected]>
Signed-off-by: Jean-Michel Hautbois <[email protected]>

---
v4:
- Add the vendor prefox for DT name
- Use the reg-names in DT parsing
- Remove MAINTAINERS entry

v3 main changes:
- Change code organization
- Remove unused variables
- Correct the fmt_meta functions
- Rewrite the start/stop streaming
- You can now start the image node alone, but not the metadata one
- The buffers are allocated per-node
- only the required stream is started, if the route exists and is
enabled
- Prefix the macros with UNICAM_ to not have too generic names
- Drop colorspace support
-> This is causing issues in the try-fmt v4l2-compliance test
test VIDIOC_G_FMT: OK
fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
test VIDIOC_TRY_FMT: FAIL
fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
test VIDIOC_S_FMT: FAIL

v2: Remove the unicam_{info,debug,error} macros and use
dev_dbg/dev_err instead.
---
drivers/media/platform/Kconfig | 1 +
drivers/media/platform/Makefile | 2 +
drivers/media/platform/bcm2835/Kconfig | 21 +
drivers/media/platform/bcm2835/Makefile | 3 +
.../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
.../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
6 files changed, 2850 insertions(+)
create mode 100644 drivers/media/platform/bcm2835/Kconfig
create mode 100644 drivers/media/platform/bcm2835/Makefile
create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c

diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 9fbdba0fd1e7..033b0358fbb8 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -170,6 +170,7 @@ source "drivers/media/platform/am437x/Kconfig"
source "drivers/media/platform/xilinx/Kconfig"
source "drivers/media/platform/rcar-vin/Kconfig"
source "drivers/media/platform/atmel/Kconfig"
+source "drivers/media/platform/bcm2835/Kconfig"
source "drivers/media/platform/sunxi/Kconfig"

config VIDEO_TI_CAL
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index 28eb4aadbf45..18894fc586aa 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -86,6 +86,8 @@ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom/camss/

obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/

+obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835/
+
obj-y += sunxi/

obj-$(CONFIG_VIDEO_MESON_GE2D) += meson/ge2d/
diff --git a/drivers/media/platform/bcm2835/Kconfig b/drivers/media/platform/bcm2835/Kconfig
new file mode 100644
index 000000000000..1691541da905
--- /dev/null
+++ b/drivers/media/platform/bcm2835/Kconfig
@@ -0,0 +1,21 @@
+# Broadcom VideoCore4 V4L2 camera support
+
+config VIDEO_BCM2835_UNICAM
+ tristate "Broadcom BCM283x/BCM271x Unicam video capture driver"
+ depends on ARCH_BCM2835 || COMPILE_TEST
+ depends on VIDEO_V4L2
+ select MEDIA_CONTROLLER
+ select VIDEO_V4L2_SUBDEV_API
+ select V4L2_FWNODE
+ select VIDEOBUF2_DMA_CONTIG
+ help
+ Say Y here to enable support for the BCM283x/BCM271x CSI-2 receiver.
+ This is a V4L2 driver that controls the CSI-2 receiver directly,
+ independently from the VC4 firmware.
+ This driver is mutually exclusive with the use of bcm2835-camera. The
+ firmware will disable all access to the peripheral from within the
+ firmware if it finds a DT node using it, and bcm2835-camera will
+ therefore fail to probe.
+
+ To compile this driver as a module, choose M here. The module will be
+ called bcm2835-unicam.
diff --git a/drivers/media/platform/bcm2835/Makefile b/drivers/media/platform/bcm2835/Makefile
new file mode 100644
index 000000000000..a98aba03598a
--- /dev/null
+++ b/drivers/media/platform/bcm2835/Makefile
@@ -0,0 +1,3 @@
+# Makefile for BCM2835 Unicam driver
+
+obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835-unicam.o
diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
new file mode 100644
index 000000000000..b8d297076a02
--- /dev/null
+++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
@@ -0,0 +1,253 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+/*
+ * Copyright (C) 2017-2020 Raspberry Pi Trading.
+ * Dave Stevenson <[email protected]>
+ */
+
+#ifndef VC4_REGS_UNICAM_H
+#define VC4_REGS_UNICAM_H
+
+/*
+ * The following values are taken from files found within the code drop
+ * made by Broadcom for the BCM21553 Graphics Driver, predominantly in
+ * brcm_usrlib/dag/vmcsx/vcinclude/hardware_vc4.h.
+ * They have been modified to be only the register offset.
+ */
+#define UNICAM_CTRL 0x000
+#define UNICAM_STA 0x004
+#define UNICAM_ANA 0x008
+#define UNICAM_PRI 0x00c
+#define UNICAM_CLK 0x010
+#define UNICAM_CLT 0x014
+#define UNICAM_DAT0 0x018
+#define UNICAM_DAT1 0x01c
+#define UNICAM_DAT2 0x020
+#define UNICAM_DAT3 0x024
+#define UNICAM_DLT 0x028
+#define UNICAM_CMP0 0x02c
+#define UNICAM_CMP1 0x030
+#define UNICAM_CAP0 0x034
+#define UNICAM_CAP1 0x038
+#define UNICAM_ICTL 0x100
+#define UNICAM_ISTA 0x104
+#define UNICAM_IDI0 0x108
+#define UNICAM_IPIPE 0x10c
+#define UNICAM_IBSA0 0x110
+#define UNICAM_IBEA0 0x114
+#define UNICAM_IBLS 0x118
+#define UNICAM_IBWP 0x11c
+#define UNICAM_IHWIN 0x120
+#define UNICAM_IHSTA 0x124
+#define UNICAM_IVWIN 0x128
+#define UNICAM_IVSTA 0x12c
+#define UNICAM_ICC 0x130
+#define UNICAM_ICS 0x134
+#define UNICAM_IDC 0x138
+#define UNICAM_IDPO 0x13c
+#define UNICAM_IDCA 0x140
+#define UNICAM_IDCD 0x144
+#define UNICAM_IDS 0x148
+#define UNICAM_DCS 0x200
+#define UNICAM_DBSA0 0x204
+#define UNICAM_DBEA0 0x208
+#define UNICAM_DBWP 0x20c
+#define UNICAM_DBCTL 0x300
+#define UNICAM_IBSA1 0x304
+#define UNICAM_IBEA1 0x308
+#define UNICAM_IDI1 0x30c
+#define UNICAM_DBSA1 0x310
+#define UNICAM_DBEA1 0x314
+#define UNICAM_MISC 0x400
+
+/*
+ * The following bitmasks are from the kernel released by Broadcom
+ * for Android - https://android.googlesource.com/kernel/bcm/
+ * The Rhea, Hawaii, and Java chips all contain the same VideoCore4
+ * Unicam block as BCM2835, as defined in eg
+ * arch/arm/mach-rhea/include/mach/rdb_A0/brcm_rdb_cam.h and similar.
+ * Values reworked to use the kernel BIT and GENMASK macros.
+ *
+ * Some of the bit mnenomics have been amended to match the datasheet.
+ */
+/* UNICAM_CTRL Register */
+#define UNICAM_CPE BIT(0)
+#define UNICAM_MEM BIT(1)
+#define UNICAM_CPR BIT(2)
+#define UNICAM_CPM_MASK GENMASK(3, 3)
+#define UNICAM_CPM_CSI2 0
+#define UNICAM_CPM_CCP2 1
+#define UNICAM_SOE BIT(4)
+#define UNICAM_DCM_MASK GENMASK(5, 5)
+#define UNICAM_DCM_STROBE 0
+#define UNICAM_DCM_DATA 1
+#define UNICAM_SLS BIT(6)
+#define UNICAM_PFT_MASK GENMASK(11, 8)
+#define UNICAM_OET_MASK GENMASK(20, 12)
+
+/* UNICAM_STA Register */
+#define UNICAM_SYN BIT(0)
+#define UNICAM_CS BIT(1)
+#define UNICAM_SBE BIT(2)
+#define UNICAM_PBE BIT(3)
+#define UNICAM_HOE BIT(4)
+#define UNICAM_PLE BIT(5)
+#define UNICAM_SSC BIT(6)
+#define UNICAM_CRCE BIT(7)
+#define UNICAM_OES BIT(8)
+#define UNICAM_IFO BIT(9)
+#define UNICAM_OFO BIT(10)
+#define UNICAM_BFO BIT(11)
+#define UNICAM_DL BIT(12)
+#define UNICAM_PS BIT(13)
+#define UNICAM_IS BIT(14)
+#define UNICAM_PI0 BIT(15)
+#define UNICAM_PI1 BIT(16)
+#define UNICAM_FSI_S BIT(17)
+#define UNICAM_FEI_S BIT(18)
+#define UNICAM_LCI_S BIT(19)
+#define UNICAM_BUF0_RDY BIT(20)
+#define UNICAM_BUF0_NO BIT(21)
+#define UNICAM_BUF1_RDY BIT(22)
+#define UNICAM_BUF1_NO BIT(23)
+#define UNICAM_DI BIT(24)
+
+#define UNICAM_STA_MASK_ALL \
+ (UNICAM_DL | \
+ UNICAM_SBE | \
+ UNICAM_PBE | \
+ UNICAM_HOE | \
+ UNICAM_PLE | \
+ UNICAM_SSC | \
+ UNICAM_CRCE | \
+ UNICAM_IFO | \
+ UNICAM_OFO | \
+ UNICAM_PS | \
+ UNICAM_PI0 | \
+ UNICAM_PI1)
+
+/* UNICAM_ANA Register */
+#define UNICAM_APD BIT(0)
+#define UNICAM_BPD BIT(1)
+#define UNICAM_AR BIT(2)
+#define UNICAM_DDL BIT(3)
+#define UNICAM_CTATADJ_MASK GENMASK(7, 4)
+#define UNICAM_PTATADJ_MASK GENMASK(11, 8)
+
+/* UNICAM_PRI Register */
+#define UNICAM_PE BIT(0)
+#define UNICAM_PT_MASK GENMASK(2, 1)
+#define UNICAM_NP_MASK GENMASK(7, 4)
+#define UNICAM_PP_MASK GENMASK(11, 8)
+#define UNICAM_BS_MASK GENMASK(15, 12)
+#define UNICAM_BL_MASK GENMASK(17, 16)
+
+/* UNICAM_CLK Register */
+#define UNICAM_CLE BIT(0)
+#define UNICAM_CLPD BIT(1)
+#define UNICAM_CLLPE BIT(2)
+#define UNICAM_CLHSE BIT(3)
+#define UNICAM_CLTRE BIT(4)
+#define UNICAM_CLAC_MASK GENMASK(8, 5)
+#define UNICAM_CLSTE BIT(29)
+
+/* UNICAM_CLT Register */
+#define UNICAM_CLT1_MASK GENMASK(7, 0)
+#define UNICAM_CLT2_MASK GENMASK(15, 8)
+
+/* UNICAM_DATn Registers */
+#define UNICAM_DLE BIT(0)
+#define UNICAM_DLPD BIT(1)
+#define UNICAM_DLLPE BIT(2)
+#define UNICAM_DLHSE BIT(3)
+#define UNICAM_DLTRE BIT(4)
+#define UNICAM_DLSM BIT(5)
+#define UNICAM_DLFO BIT(28)
+#define UNICAM_DLSTE BIT(29)
+
+#define UNICAM_DAT_MASK_ALL (UNICAM_DLSTE | UNICAM_DLFO)
+
+/* UNICAM_DLT Register */
+#define UNICAM_DLT1_MASK GENMASK(7, 0)
+#define UNICAM_DLT2_MASK GENMASK(15, 8)
+#define UNICAM_DLT3_MASK GENMASK(23, 16)
+
+/* UNICAM_ICTL Register */
+#define UNICAM_FSIE BIT(0)
+#define UNICAM_FEIE BIT(1)
+#define UNICAM_IBOB BIT(2)
+#define UNICAM_FCM BIT(3)
+#define UNICAM_TFC BIT(4)
+#define UNICAM_LIP_MASK GENMASK(6, 5)
+#define UNICAM_LCIE_MASK GENMASK(28, 16)
+
+/* UNICAM_IDI0/1 Register */
+#define UNICAM_ID0_MASK GENMASK(7, 0)
+#define UNICAM_ID1_MASK GENMASK(15, 8)
+#define UNICAM_ID2_MASK GENMASK(23, 16)
+#define UNICAM_ID3_MASK GENMASK(31, 24)
+
+/* UNICAM_ISTA Register */
+#define UNICAM_FSI BIT(0)
+#define UNICAM_FEI BIT(1)
+#define UNICAM_LCI BIT(2)
+
+#define UNICAM_ISTA_MASK_ALL (UNICAM_FSI | UNICAM_FEI | UNICAM_LCI)
+
+/* UNICAM_IPIPE Register */
+#define UNICAM_PUM_MASK GENMASK(2, 0)
+/* Unpacking modes */
+#define UNICAM_PUM_NONE 0
+#define UNICAM_PUM_UNPACK6 1
+#define UNICAM_PUM_UNPACK7 2
+#define UNICAM_PUM_UNPACK8 3
+#define UNICAM_PUM_UNPACK10 4
+#define UNICAM_PUM_UNPACK12 5
+#define UNICAM_PUM_UNPACK14 6
+#define UNICAM_PUM_UNPACK16 7
+#define UNICAM_DDM_MASK GENMASK(6, 3)
+#define UNICAM_PPM_MASK GENMASK(9, 7)
+/* Packing modes */
+#define UNICAM_PPM_NONE 0
+#define UNICAM_PPM_PACK8 1
+#define UNICAM_PPM_PACK10 2
+#define UNICAM_PPM_PACK12 3
+#define UNICAM_PPM_PACK14 4
+#define UNICAM_PPM_PACK16 5
+#define UNICAM_DEM_MASK GENMASK(11, 10)
+#define UNICAM_DEBL_MASK GENMASK(14, 12)
+#define UNICAM_ICM_MASK GENMASK(16, 15)
+#define UNICAM_IDM_MASK GENMASK(17, 17)
+
+/* UNICAM_ICC Register */
+#define UNICAM_ICFL_MASK GENMASK(4, 0)
+#define UNICAM_ICFH_MASK GENMASK(9, 5)
+#define UNICAM_ICST_MASK GENMASK(12, 10)
+#define UNICAM_ICLT_MASK GENMASK(15, 13)
+#define UNICAM_ICLL_MASK GENMASK(31, 16)
+
+/* UNICAM_DCS Register */
+#define UNICAM_DIE BIT(0)
+#define UNICAM_DIM BIT(1)
+#define UNICAM_DBOB BIT(3)
+#define UNICAM_FDE BIT(4)
+#define UNICAM_LDP BIT(5)
+#define UNICAM_EDL_MASK GENMASK(15, 8)
+
+/* UNICAM_DBCTL Register */
+#define UNICAM_DBEN BIT(0)
+#define UNICAM_BUF0_IE BIT(1)
+#define UNICAM_BUF1_IE BIT(2)
+
+/* UNICAM_CMP[0,1] register */
+#define UNICAM_PCE BIT(31)
+#define UNICAM_GI BIT(9)
+#define UNICAM_CPH BIT(8)
+#define UNICAM_PCVC_MASK GENMASK(7, 6)
+#define UNICAM_PCDT_MASK GENMASK(5, 0)
+
+/* UNICAM_MISC register */
+#define UNICAM_FL0 BIT(6)
+#define UNICAM_FL1 BIT(9)
+
+#endif
diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam.c b/drivers/media/platform/bcm2835/bcm2835-unicam.c
new file mode 100644
index 000000000000..470e691637c7
--- /dev/null
+++ b/drivers/media/platform/bcm2835/bcm2835-unicam.c
@@ -0,0 +1,2570 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * BCM283x / BCM271x Unicam Capture Driver
+ *
+ * Copyright (C) 2017-2020 - Raspberry Pi (Trading) Ltd.
+ *
+ * Dave Stevenson <[email protected]>
+ *
+ * Based on TI am437x driver by
+ * Benoit Parrot <[email protected]>
+ * Lad, Prabhakar <[email protected]>
+ *
+ * and TI CAL camera interface driver by
+ * Benoit Parrot <[email protected]>
+ *
+ *
+ * There are two camera drivers in the kernel for BCM283x - this one
+ * and bcm2835-camera (currently in staging).
+ *
+ * This driver directly controls the Unicam peripheral - there is no
+ * involvement with the VideoCore firmware. Unicam receives CSI-2 or
+ * CCP2 data and writes it into SDRAM.
+ * The only potential processing options are to repack Bayer data into an
+ * alternate format, and applying windowing.
+ * The repacking does not shift the data, so can repack V4L2_PIX_FMT_Sxxxx10P
+ * to V4L2_PIX_FMT_Sxxxx10, or V4L2_PIX_FMT_Sxxxx12P to V4L2_PIX_FMT_Sxxxx12,
+ * but not generically up to V4L2_PIX_FMT_Sxxxx16. The driver will add both
+ * formats where the relevant formats are defined, and will automatically
+ * configure the repacking as required.
+ * Support for windowing may be added later.
+ *
+ * It should be possible to connect this driver to any sensor with a
+ * suitable output interface and V4L2 subdevice driver.
+ *
+ * bcm2835-camera uses the VideoCore firmware to control the sensor,
+ * Unicam, ISP, and all tuner control loops. Fully processed frames are
+ * delivered to the driver by the firmware. It only has sensor drivers
+ * for Omnivision OV5647, and Sony IMX219 sensors.
+ *
+ * The two drivers are mutually exclusive for the same Unicam instance.
+ * The VideoCore firmware checks the device tree configuration during boot.
+ * If it finds device tree nodes called csi0 or csi1 it will block the
+ * firmware from accessing the peripheral, and bcm2835-camera will
+ * not be able to stream data.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-async.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-dv-timings.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "bcm2835-unicam-regs.h"
+
+#define UNICAM_MODULE_NAME "unicam"
+
+/*
+ * Unicam must request a minimum of 250Mhz from the VPU clock.
+ * Otherwise the input FIFOs overrun and cause image corruption.
+ */
+#define UNICAM_MIN_VPU_CLOCK_RATE (250 * 1000 * 1000)
+
+/*
+ * Stride is a 16 bit register, but also has to be a multiple of 32.
+ */
+#define UNICAM_BPL_ALIGNMENT 32
+#define UNICAM_MAX_BYTESPERLINE ((1 << 16) - UNICAM_BPL_ALIGNMENT)
+/*
+ * Max width is therefore determined by the max stride divided by
+ * the number of bits per pixel. Take 32bpp as a
+ * worst case.
+ * No imposed limit on the height, so adopt a square image for want
+ * of anything better.
+ */
+#define UNICAM_MAX_WIDTH (UNICAM_MAX_BYTESPERLINE / 4)
+#define UNICAM_MAX_HEIGHT UNICAM_MAX_WIDTH
+/* Define a nominal minimum image size */
+#define UNICAM_MIN_WIDTH 16
+#define UNICAM_MIN_HEIGHT 16
+/* Default size of the embedded buffer */
+#define UNICAM_EMBEDDED_SIZE 16384
+
+/*
+ * Size of the dummy buffer. Can be any size really, but the DMA
+ * allocation works in units of page sizes.
+ */
+#define UNICAM_DUMMY_BUF_SIZE PAGE_SIZE
+
+#define UNICAM_SD_PAD_SINK 0
+#define UNICAM_SD_PAD_SOURCE_IMAGE 1
+#define UNICAM_SD_PAD_SOURCE_METADATA 2
+#define UNICAM_SD_NUM_PADS (1 + UNICAM_SD_PAD_SOURCE_METADATA)
+
+static inline bool unicam_sd_pad_is_sink(u32 pad)
+{
+ /* Camera RX has 1 sink pad, and N source pads */
+ return pad == 0;
+}
+
+static inline bool unicam_sd_pad_is_source(u32 pad)
+{
+ /* Camera RX has 1 sink pad, and N source pads */
+ return pad != UNICAM_SD_PAD_SINK;
+}
+
+enum unicam_node_type {
+ UNICAM_IMAGE_NODE,
+ UNICAM_METADATA_NODE,
+ UNICAM_MAX_NODES
+};
+
+/* -----------------------------------------------------------------------------
+ * Structure definitions
+ */
+
+/*
+ * struct unicam_fmt - Unicam media bus format information
+ * @fourcc: V4L2 pixel format FCC identifier. 0 if n/a.
+ * @unpacked_fourcc: V4L2 pixel format FCC identifier if the data is expanded
+ * out to 16bpp. 0 if n/a.
+ * @code: V4L2 media bus format code.
+ * @depth: Bits per pixel as delivered from the source.
+ * @csi_dt: CSI data type.
+ * @metadata_fmt: This format only applies to the metadata pad.
+ */
+struct unicam_fmt {
+ u32 fourcc;
+ u32 unpacked_fourcc;
+ u32 code;
+ u8 depth;
+ u8 csi_dt;
+ u8 metadata_fmt:1;
+};
+
+static const struct unicam_fmt unicam_image_formats[] = {
+ /* YUV Formats */
+ {
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .code = MEDIA_BUS_FMT_YUYV8_1X16,
+ .depth = 16,
+ .csi_dt = 0x1e,
+ }, {
+ .fourcc = V4L2_PIX_FMT_UYVY,
+ .code = MEDIA_BUS_FMT_UYVY8_1X16,
+ .depth = 16,
+ .csi_dt = 0x1e,
+ }, {
+ .fourcc = V4L2_PIX_FMT_YVYU,
+ .code = MEDIA_BUS_FMT_YVYU8_1X16,
+ .depth = 16,
+ .csi_dt = 0x1e,
+ }, {
+ .fourcc = V4L2_PIX_FMT_VYUY,
+ .code = MEDIA_BUS_FMT_VYUY8_1X16,
+ .depth = 16,
+ .csi_dt = 0x1e,
+ }, {
+ /* RGB Formats */
+ .fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */
+ .code = MEDIA_BUS_FMT_RGB565_1X16,
+ .depth = 16,
+ .csi_dt = 0x22,
+ }, {
+ .fourcc = V4L2_PIX_FMT_RGB24, /* rgb */
+ .code = MEDIA_BUS_FMT_RGB888_1X24,
+ .depth = 24,
+ .csi_dt = 0x24,
+ }, {
+ .fourcc = V4L2_PIX_FMT_BGR24, /* bgr */
+ .code = MEDIA_BUS_FMT_BGR888_1X24,
+ .depth = 24,
+ .csi_dt = 0x24,
+ }, {
+ /* Bayer Formats */
+ .fourcc = V4L2_PIX_FMT_SBGGR8,
+ .code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .depth = 8,
+ .csi_dt = 0x2a,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGBRG8,
+ .code = MEDIA_BUS_FMT_SGBRG8_1X8,
+ .depth = 8,
+ .csi_dt = 0x2a,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG8,
+ .code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .depth = 8,
+ .csi_dt = 0x2a,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SRGGB8,
+ .code = MEDIA_BUS_FMT_SRGGB8_1X8,
+ .depth = 8,
+ .csi_dt = 0x2a,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SBGGR10P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SBGGR10,
+ .code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .depth = 10,
+ .csi_dt = 0x2b,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGBRG10P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SGBRG10,
+ .code = MEDIA_BUS_FMT_SGBRG10_1X10,
+ .depth = 10,
+ .csi_dt = 0x2b,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG10P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SGRBG10,
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .depth = 10,
+ .csi_dt = 0x2b,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SRGGB10P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SRGGB10,
+ .code = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .depth = 10,
+ .csi_dt = 0x2b,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SBGGR12P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SBGGR12,
+ .code = MEDIA_BUS_FMT_SBGGR12_1X12,
+ .depth = 12,
+ .csi_dt = 0x2c,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGBRG12P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SGBRG12,
+ .code = MEDIA_BUS_FMT_SGBRG12_1X12,
+ .depth = 12,
+ .csi_dt = 0x2c,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG12P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SGRBG12,
+ .code = MEDIA_BUS_FMT_SGRBG12_1X12,
+ .depth = 12,
+ .csi_dt = 0x2c,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SRGGB12P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SRGGB12,
+ .code = MEDIA_BUS_FMT_SRGGB12_1X12,
+ .depth = 12,
+ .csi_dt = 0x2c,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SBGGR14P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SBGGR14,
+ .code = MEDIA_BUS_FMT_SBGGR14_1X14,
+ .depth = 14,
+ .csi_dt = 0x2d,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGBRG14P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SGBRG14,
+ .code = MEDIA_BUS_FMT_SGBRG14_1X14,
+ .depth = 14,
+ .csi_dt = 0x2d,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SGRBG14P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SGRBG14,
+ .code = MEDIA_BUS_FMT_SGRBG14_1X14,
+ .depth = 14,
+ .csi_dt = 0x2d,
+ }, {
+ .fourcc = V4L2_PIX_FMT_SRGGB14P,
+ .unpacked_fourcc = V4L2_PIX_FMT_SRGGB14,
+ .code = MEDIA_BUS_FMT_SRGGB14_1X14,
+ .depth = 14,
+ .csi_dt = 0x2d,
+ }, {
+ /* 16 bit Bayer formats could be supported. */
+
+ /* Greyscale formats */
+ .fourcc = V4L2_PIX_FMT_GREY,
+ .code = MEDIA_BUS_FMT_Y8_1X8,
+ .depth = 8,
+ .csi_dt = 0x2a,
+ }, {
+ .fourcc = V4L2_PIX_FMT_Y10P,
+ .unpacked_fourcc = V4L2_PIX_FMT_Y10,
+ .code = MEDIA_BUS_FMT_Y10_1X10,
+ .depth = 10,
+ .csi_dt = 0x2b,
+ }, {
+ .fourcc = V4L2_PIX_FMT_Y12P,
+ .unpacked_fourcc = V4L2_PIX_FMT_Y12,
+ .code = MEDIA_BUS_FMT_Y12_1X12,
+ .depth = 12,
+ .csi_dt = 0x2c,
+ }, {
+ .fourcc = V4L2_PIX_FMT_Y14P,
+ .unpacked_fourcc = V4L2_PIX_FMT_Y14,
+ .code = MEDIA_BUS_FMT_Y14_1X14,
+ .depth = 14,
+ .csi_dt = 0x2d,
+ },
+};
+
+static const struct unicam_fmt unicam_meta_formats[] = {
+ {
+ .fourcc = V4L2_META_FMT_8,
+ .code = MEDIA_BUS_FMT_METADATA_8,
+ .depth = 8,
+ .metadata_fmt = 1,
+ }
+};
+
+static const struct v4l2_mbus_framefmt default_image_format = {
+ .width = 640,
+ .height = 480,
+ .code = MEDIA_BUS_FMT_UYVY8_1X16,
+ .field = V4L2_FIELD_NONE,
+ .colorspace = V4L2_COLORSPACE_SRGB,
+ .ycbcr_enc = V4L2_YCBCR_ENC_601,
+ .quantization = V4L2_QUANTIZATION_LIM_RANGE,
+ .xfer_func = V4L2_XFER_FUNC_SRGB,
+ .flags = 0,
+};
+
+struct unicam_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct list_head list;
+};
+
+static inline struct unicam_buffer *to_unicam_buffer(struct vb2_buffer *vb)
+{
+ return container_of(vb, struct unicam_buffer, vb.vb2_buf);
+}
+
+struct unicam_node {
+ bool registered;
+ bool streaming;
+ unsigned int id;
+
+ /* Pointer to the current v4l2_buffer */
+ struct unicam_buffer *cur_frm;
+ /* Pointer to the next v4l2_buffer */
+ struct unicam_buffer *next_frm;
+ /* video capture */
+ const struct unicam_fmt *fmt;
+ /* Used to store current pixel format */
+ struct v4l2_format v_fmt;
+ /* Buffer queue used in video-buf */
+ struct vb2_queue buffer_queue;
+ /* Queue of filled frames */
+ struct list_head dma_queue;
+ /* IRQ lock for DMA queue */
+ spinlock_t dma_queue_lock;
+ /* lock used to access this structure */
+ struct mutex lock;
+ /* Identifies video device for this channel */
+ struct video_device video_dev;
+ /* Pointer to the parent handle */
+ struct unicam_device *dev;
+ struct media_pad pad;
+ /*
+ * Dummy buffer intended to be used by unicam
+ * if we have no other queued buffers to swap to.
+ */
+ void *dummy_buf_cpu_addr;
+ dma_addr_t dummy_buf_dma_addr;
+};
+
+struct unicam_device {
+ struct kref kref;
+
+ /* peripheral base address */
+ void __iomem *base;
+ /* clock gating base address */
+ void __iomem *clk_gate_base;
+ /* lp clock handle */
+ struct clk *clock;
+ /* vpu clock handle */
+ struct clk *vpu_clock;
+ /* V4l2 device */
+ struct v4l2_device v4l2_dev;
+ struct media_device mdev;
+
+ /* parent device */
+ struct device *dev;
+ /* subdevice async Notifier */
+ struct v4l2_async_notifier notifier;
+ unsigned int sequence;
+
+ /* Sensor node */
+ struct {
+ struct v4l2_subdev *subdev;
+ struct media_pad *pad;
+ } sensor;
+
+ /* Internal subdev */
+ struct {
+ struct v4l2_subdev sd;
+ struct media_pad pads[UNICAM_SD_NUM_PADS];
+ bool streaming;
+ } subdev;
+
+ enum v4l2_mbus_type bus_type;
+ /*
+ * Stores bus.mipi_csi2.flags for CSI2 sensors, or
+ * bus.mipi_csi1.strobe for CCP2.
+ */
+ unsigned int bus_flags;
+ unsigned int max_data_lanes;
+ unsigned int active_data_lanes;
+
+ struct media_pipeline pipe;
+
+ struct unicam_node node[UNICAM_MAX_NODES];
+};
+
+static inline struct unicam_device *
+notifier_to_unicam_device(struct v4l2_async_notifier *notifier)
+{
+ return container_of(notifier, struct unicam_device, notifier);
+}
+
+static inline struct unicam_device *
+sd_to_unicam_device(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct unicam_device, subdev.sd);
+}
+
+static void unicam_release(struct kref *kref)
+{
+ struct unicam_device *unicam =
+ container_of(kref, struct unicam_device, kref);
+
+ if (unicam->mdev.dev)
+ media_device_cleanup(&unicam->mdev);
+
+ kfree(unicam);
+}
+
+static void unicam_put(struct unicam_device *unicam)
+{
+ kref_put(&unicam->kref, unicam_release);
+}
+
+static struct unicam_device *unicam_get(struct unicam_device *unicam)
+{
+ kref_get(&unicam->kref);
+ return unicam;
+}
+
+static void unicam_node_release(struct video_device *vdev)
+{
+ struct unicam_node *node = video_get_drvdata(vdev);
+
+ unicam_put(node->dev);
+}
+
+/* -----------------------------------------------------------------------------
+ * Misc helper functions
+ */
+
+static inline bool is_metadata_node(struct unicam_node *node)
+{
+ return node->video_dev.device_caps & V4L2_CAP_META_CAPTURE;
+}
+
+static inline bool is_image_node(struct unicam_node *node)
+{
+ return node->video_dev.device_caps & V4L2_CAP_VIDEO_CAPTURE;
+}
+
+/* Hardware access */
+static inline void unicam_clk_write(struct unicam_device *unicam, u32 val)
+{
+ /* Pass the CM_PASSWORD along with the value. */
+ writel(val | 0x5a000000, unicam->clk_gate_base);
+}
+
+static inline u32 unicam_reg_read(struct unicam_device *unicam, u32 offset)
+{
+ return readl(unicam->base + offset);
+}
+
+static inline void unicam_reg_write(struct unicam_device *unicam, u32 offset, u32 val)
+{
+ writel(val, unicam->base + offset);
+}
+
+static inline int unicam_get_field(u32 value, u32 mask)
+{
+ return (value & mask) >> __ffs(mask);
+}
+
+static inline void unicam_set_field(u32 *valp, u32 field, u32 mask)
+{
+ u32 val = *valp;
+
+ val &= ~mask;
+ val |= (field << __ffs(mask)) & mask;
+ *valp = val;
+}
+
+static inline void unicam_reg_write_field(struct unicam_device *unicam, u32 offset,
+ u32 field, u32 mask)
+{
+ u32 val = unicam_reg_read(unicam, offset);
+
+ unicam_set_field(&val, field, mask);
+ unicam_reg_write(unicam, offset, val);
+}
+
+/* -----------------------------------------------------------------------------
+ * Format data table and helper functions
+ */
+
+/* Format setup functions */
+static const struct unicam_fmt *unicam_find_format_by_code(u32 code, u32 pad)
+{
+ const struct unicam_fmt *formats;
+ unsigned int num_formats;
+ unsigned int i;
+
+ if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
+ formats = unicam_image_formats;
+ num_formats = ARRAY_SIZE(unicam_image_formats);
+ } else {
+ formats = unicam_meta_formats;
+ num_formats = ARRAY_SIZE(unicam_meta_formats);
+ }
+
+ for (i = 0; i < num_formats; i++) {
+ if (formats[i].code == code)
+ return &formats[i];
+ }
+
+ return NULL;
+}
+
+static const struct unicam_fmt *unicam_find_format_by_fourcc(u32 fourcc, u32 pad)
+{
+ const struct unicam_fmt *formats;
+ unsigned int num_formats;
+ unsigned int i;
+
+ if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
+ formats = unicam_image_formats;
+ num_formats = ARRAY_SIZE(unicam_image_formats);
+ } else {
+ formats = unicam_meta_formats;
+ num_formats = ARRAY_SIZE(unicam_meta_formats);
+ }
+
+ for (i = 0; i < num_formats; ++i) {
+ if (formats[i].fourcc == fourcc)
+ return &formats[i];
+ }
+
+ return NULL;
+}
+
+static int unicam_calc_format_size_bpl(struct unicam_device *unicam,
+ const struct unicam_fmt *fmt,
+ struct v4l2_format *f)
+{
+ u32 min_bpl;
+
+ v4l_bound_align_image(&f->fmt.pix.width, UNICAM_MIN_WIDTH,
+ UNICAM_MAX_WIDTH, 2,
+ &f->fmt.pix.height, UNICAM_MIN_HEIGHT,
+ UNICAM_MAX_HEIGHT, 0, 0);
+
+ /* Unpacking always goes to 16bpp */
+ if (f->fmt.pix.pixelformat == fmt->unpacked_fourcc)
+ min_bpl = f->fmt.pix.width * 2;
+ else
+ min_bpl = f->fmt.pix.width * fmt->depth / 8;
+ min_bpl = ALIGN(min_bpl, UNICAM_BPL_ALIGNMENT);
+
+ f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline,
+ UNICAM_BPL_ALIGNMENT);
+ f->fmt.pix.bytesperline = clamp_t(unsigned int,
+ f->fmt.pix.bytesperline,
+ min_bpl,
+ UNICAM_MAX_BYTESPERLINE);
+
+ f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
+
+ dev_dbg(unicam->dev, "%s: fourcc: %08X size: %ux%u bpl:%u img_size:%u\n",
+ __func__,
+ f->fmt.pix.pixelformat,
+ f->fmt.pix.width, f->fmt.pix.height,
+ f->fmt.pix.bytesperline, f->fmt.pix.sizeimage);
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Hardware handling
+ */
+
+static void unicam_wr_dma_addr(struct unicam_node *node, dma_addr_t dmaaddr,
+ unsigned int buffer_size)
+{
+ dma_addr_t endaddr = dmaaddr + buffer_size;
+
+ if (node->id == UNICAM_IMAGE_NODE) {
+ unicam_reg_write(node->dev, UNICAM_IBSA0, dmaaddr);
+ unicam_reg_write(node->dev, UNICAM_IBEA0, endaddr);
+ } else {
+ unicam_reg_write(node->dev, UNICAM_DBSA0, dmaaddr);
+ unicam_reg_write(node->dev, UNICAM_DBEA0, endaddr);
+ }
+}
+
+static unsigned int unicam_get_lines_done(struct unicam_device *unicam)
+{
+ dma_addr_t start_addr, cur_addr;
+ struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
+ unsigned int stride = node->v_fmt.fmt.pix.bytesperline;
+ struct unicam_buffer *frm = node->cur_frm;
+
+ if (!frm)
+ return 0;
+
+ start_addr = vb2_dma_contig_plane_dma_addr(&frm->vb.vb2_buf, 0);
+ cur_addr = unicam_reg_read(unicam, UNICAM_IBWP);
+ return (unsigned int)(cur_addr - start_addr) / stride;
+}
+
+static void unicam_schedule_next_buffer(struct unicam_node *node)
+{
+ struct unicam_buffer *buf;
+ unsigned int size;
+ dma_addr_t addr;
+
+ buf = list_first_entry(&node->dma_queue, struct unicam_buffer, list);
+ node->next_frm = buf;
+ list_del(&buf->list);
+
+ addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
+ if (is_image_node(node))
+ size = node->v_fmt.fmt.pix.sizeimage;
+ else
+ size = node->v_fmt.fmt.meta.buffersize;
+
+ unicam_wr_dma_addr(node, addr, size);
+}
+
+static void unicam_schedule_dummy_buffer(struct unicam_node *node)
+{
+ struct unicam_device *unicam = node->dev;
+ int node_id = is_image_node(node) ? UNICAM_IMAGE_NODE : UNICAM_METADATA_NODE;
+
+ dev_dbg(unicam->dev, "Scheduling dummy buffer for node %d\n", node_id);
+
+ unicam_wr_dma_addr(node, node->dummy_buf_dma_addr,
+ UNICAM_DUMMY_BUF_SIZE);
+
+ node->next_frm = NULL;
+}
+
+static void unicam_process_buffer_complete(struct unicam_node *node,
+ unsigned int sequence)
+{
+ node->cur_frm->vb.field = node->v_fmt.fmt.pix.field;
+ node->cur_frm->vb.sequence = sequence;
+
+ vb2_buffer_done(&node->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE);
+}
+
+static void unicam_queue_event_sof(struct unicam_device *unicam)
+{
+ struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
+
+ struct v4l2_event event = {
+ .type = V4L2_EVENT_FRAME_SYNC,
+ .u.frame_sync.frame_sequence = unicam->sequence,
+ };
+
+ v4l2_event_queue(&node->video_dev, &event);
+}
+
+/*
+ * unicam_isr : ISR handler for unicam capture
+ * @irq: irq number
+ * @dev_id: dev_id ptr
+ *
+ * It changes status of the captured buffer, takes next buffer from the queue
+ * and sets its address in unicam registers
+ */
+static irqreturn_t unicam_isr(int irq, void *dev)
+{
+ struct unicam_device *unicam = dev;
+ unsigned int lines_done = unicam_get_lines_done(dev);
+ unsigned int sequence = unicam->sequence;
+ unsigned int i;
+ u32 ista, sta;
+ bool fe;
+ u64 ts;
+
+ sta = unicam_reg_read(unicam, UNICAM_STA);
+ /* Write value back to clear the interrupts */
+ unicam_reg_write(unicam, UNICAM_STA, sta);
+
+ ista = unicam_reg_read(unicam, UNICAM_ISTA);
+ /* Write value back to clear the interrupts */
+ unicam_reg_write(unicam, UNICAM_ISTA, ista);
+
+ dev_dbg(unicam->dev, "ISR: ISTA: 0x%X, STA: 0x%X, sequence %d, lines done %d",
+ ista, sta, sequence, lines_done);
+
+ if (!(sta & (UNICAM_IS | UNICAM_PI0)))
+ return IRQ_HANDLED;
+
+ /*
+ * Look for either the Frame End interrupt or the Packet Capture status
+ * to signal a frame end.
+ */
+ fe = ista & UNICAM_FEI || sta & UNICAM_PI0;
+
+ /*
+ * We must run the frame end handler first. If we have a valid next_frm
+ * and we get a simultaneout FE + FS interrupt, running the FS handler
+ * first would null out the next_frm ptr and we would have lost the
+ * buffer forever.
+ */
+ if (fe) {
+ /*
+ * Ensure we have swapped buffers already as we can't
+ * stop the peripheral. If no buffer is available, use a
+ * dummy buffer to dump out frames until we get a new buffer
+ * to use.
+ */
+ for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
+ if (!unicam->node[i].streaming)
+ continue;
+
+ /*
+ * If cur_frm == next_frm, it means we have not had
+ * a chance to swap buffers, likely due to having
+ * multiple interrupts occurring simultaneously (like FE
+ * + FS + LS). In this case, we cannot signal the buffer
+ * as complete, as the HW will reuse that buffer.
+ */
+ if (unicam->node[i].cur_frm &&
+ unicam->node[i].cur_frm != unicam->node[i].next_frm)
+ unicam_process_buffer_complete(&unicam->node[i],
+ sequence);
+ unicam->node[i].cur_frm = unicam->node[i].next_frm;
+ }
+ unicam->sequence++;
+ }
+
+ if (ista & UNICAM_FSI) {
+ /*
+ * Timestamp is to be when the first data byte was captured,
+ * aka frame start.
+ */
+ ts = ktime_get_ns();
+ for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
+ if (!unicam->node[i].streaming)
+ continue;
+
+ if (unicam->node[i].cur_frm)
+ unicam->node[i].cur_frm->vb.vb2_buf.timestamp =
+ ts;
+ else
+ dev_dbg(unicam->v4l2_dev.dev,
+ "ISR: [%d] Dropping frame, buffer not available at FS\n",
+ i);
+ /*
+ * Set the next frame output to go to a dummy frame
+ * if we have not managed to obtain another frame
+ * from the queue.
+ */
+ unicam_schedule_dummy_buffer(&unicam->node[i]);
+ }
+
+ unicam_queue_event_sof(unicam);
+ }
+
+ /*
+ * Cannot swap buffer at frame end, there may be a race condition
+ * where the HW does not actually swap it if the new frame has
+ * already started.
+ */
+ if (ista & (UNICAM_FSI | UNICAM_LCI) && !fe) {
+ for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
+ if (!unicam->node[i].streaming)
+ continue;
+
+ spin_lock(&unicam->node[i].dma_queue_lock);
+ if (!list_empty(&unicam->node[i].dma_queue) &&
+ !unicam->node[i].next_frm)
+ unicam_schedule_next_buffer(&unicam->node[i]);
+ spin_unlock(&unicam->node[i].dma_queue_lock);
+ }
+ }
+
+ if (unicam_reg_read(unicam, UNICAM_ICTL) & UNICAM_FCM) {
+ /* Switch out of trigger mode if selected */
+ unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_TFC);
+ unicam_reg_write_field(unicam, UNICAM_ICTL, 0, UNICAM_FCM);
+ }
+ return IRQ_HANDLED;
+}
+
+static void unicam_set_packing_config(struct unicam_device *unicam)
+{
+ struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
+ u32 pack, unpack;
+ u32 val;
+
+ if (node->v_fmt.fmt.pix.pixelformat ==
+ node->fmt->fourcc) {
+ unpack = UNICAM_PUM_NONE;
+ pack = UNICAM_PPM_NONE;
+ } else {
+ switch (node->fmt->depth) {
+ case 8:
+ unpack = UNICAM_PUM_UNPACK8;
+ break;
+ case 10:
+ unpack = UNICAM_PUM_UNPACK10;
+ break;
+ case 12:
+ unpack = UNICAM_PUM_UNPACK12;
+ break;
+ case 14:
+ unpack = UNICAM_PUM_UNPACK14;
+ break;
+ case 16:
+ unpack = UNICAM_PUM_UNPACK16;
+ break;
+ default:
+ unpack = UNICAM_PUM_NONE;
+ break;
+ }
+
+ /* Repacking is always to 16bpp */
+ pack = UNICAM_PPM_PACK16;
+ }
+
+ val = 0;
+ unicam_set_field(&val, unpack, UNICAM_PUM_MASK);
+ unicam_set_field(&val, pack, UNICAM_PPM_MASK);
+ unicam_reg_write(unicam, UNICAM_IPIPE, val);
+}
+
+static void unicam_cfg_image_id(struct unicam_device *unicam)
+{
+ struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
+
+ if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
+ /* CSI2 mode, hardcode VC 0 for now. */
+ unicam_reg_write(unicam, UNICAM_IDI0,
+ (0 << 6) | node->fmt->csi_dt);
+ } else {
+ /* CCP2 mode */
+ unicam_reg_write(unicam, UNICAM_IDI0,
+ 0x80 | node->fmt->csi_dt);
+ }
+}
+
+static void unicam_enable_ed(struct unicam_device *unicam)
+{
+ u32 val = unicam_reg_read(unicam, UNICAM_DCS);
+
+ unicam_set_field(&val, 2, UNICAM_EDL_MASK);
+ /* Do not wrap at the end of the embedded data buffer */
+ unicam_set_field(&val, 0, UNICAM_DBOB);
+
+ unicam_reg_write(unicam, UNICAM_DCS, val);
+}
+
+static void unicam_start_rx(struct unicam_device *unicam, dma_addr_t *addr)
+{
+ struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
+ int line_int_freq = node->v_fmt.fmt.pix.height >> 2;
+ unsigned int size, i;
+ u32 val;
+
+ if (line_int_freq < 128)
+ line_int_freq = 128;
+
+ /* Enable lane clocks */
+ val = 1;
+ for (i = 0; i < unicam->active_data_lanes; i++)
+ val = val << 2 | 1;
+ unicam_clk_write(unicam, val);
+
+ /* Basic init */
+ unicam_reg_write(unicam, UNICAM_CTRL, UNICAM_MEM);
+
+ /* Enable analogue control, and leave in reset. */
+ val = UNICAM_AR;
+ unicam_set_field(&val, 7, UNICAM_CTATADJ_MASK);
+ unicam_set_field(&val, 7, UNICAM_PTATADJ_MASK);
+ unicam_reg_write(unicam, UNICAM_ANA, val);
+ usleep_range(1000, 2000);
+
+ /* Come out of reset */
+ unicam_reg_write_field(unicam, UNICAM_ANA, 0, UNICAM_AR);
+
+ /* Peripheral reset */
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPR);
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPR);
+
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPE);
+
+ /* Enable Rx control. */
+ val = unicam_reg_read(unicam, UNICAM_CTRL);
+ if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
+ unicam_set_field(&val, UNICAM_CPM_CSI2, UNICAM_CPM_MASK);
+ unicam_set_field(&val, UNICAM_DCM_STROBE, UNICAM_DCM_MASK);
+ } else {
+ unicam_set_field(&val, UNICAM_CPM_CCP2, UNICAM_CPM_MASK);
+ unicam_set_field(&val, unicam->bus_flags, UNICAM_DCM_MASK);
+ }
+ /* Packet framer timeout */
+ unicam_set_field(&val, 0xf, UNICAM_PFT_MASK);
+ unicam_set_field(&val, 128, UNICAM_OET_MASK);
+ unicam_reg_write(unicam, UNICAM_CTRL, val);
+
+ unicam_reg_write(unicam, UNICAM_IHWIN, 0);
+ unicam_reg_write(unicam, UNICAM_IVWIN, 0);
+
+ /* AXI bus access QoS setup */
+ val = unicam_reg_read(unicam, UNICAM_PRI);
+ unicam_set_field(&val, 0, UNICAM_BL_MASK);
+ unicam_set_field(&val, 0, UNICAM_BS_MASK);
+ unicam_set_field(&val, 0xe, UNICAM_PP_MASK);
+ unicam_set_field(&val, 8, UNICAM_NP_MASK);
+ unicam_set_field(&val, 2, UNICAM_PT_MASK);
+ unicam_set_field(&val, 1, UNICAM_PE);
+ unicam_reg_write(unicam, UNICAM_PRI, val);
+
+ unicam_reg_write_field(unicam, UNICAM_ANA, 0, UNICAM_DDL);
+
+ /* Always start in trigger frame capture mode (UNICAM_FCM set) */
+ val = UNICAM_FSIE | UNICAM_FEIE | UNICAM_FCM | UNICAM_IBOB;
+ unicam_set_field(&val, line_int_freq, UNICAM_LCIE_MASK);
+ unicam_reg_write(unicam, UNICAM_ICTL, val);
+ unicam_reg_write(unicam, UNICAM_STA, UNICAM_STA_MASK_ALL);
+ unicam_reg_write(unicam, UNICAM_ISTA, UNICAM_ISTA_MASK_ALL);
+
+ /* tclk_term_en */
+ unicam_reg_write_field(unicam, UNICAM_CLT, 2, UNICAM_CLT1_MASK);
+ /* tclk_settle */
+ unicam_reg_write_field(unicam, UNICAM_CLT, 6, UNICAM_CLT2_MASK);
+ /* td_term_en */
+ unicam_reg_write_field(unicam, UNICAM_DLT, 2, UNICAM_DLT1_MASK);
+ /* ths_settle */
+ unicam_reg_write_field(unicam, UNICAM_DLT, 6, UNICAM_DLT2_MASK);
+ /* trx_enable */
+ unicam_reg_write_field(unicam, UNICAM_DLT, 0, UNICAM_DLT3_MASK);
+
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_SOE);
+
+ /* Packet compare setup - required to avoid missing frame ends */
+ val = 0;
+ unicam_set_field(&val, 1, UNICAM_PCE);
+ unicam_set_field(&val, 1, UNICAM_GI);
+ unicam_set_field(&val, 1, UNICAM_CPH);
+ unicam_set_field(&val, 0, UNICAM_PCVC_MASK);
+ unicam_set_field(&val, 1, UNICAM_PCDT_MASK);
+ unicam_reg_write(unicam, UNICAM_CMP0, val);
+
+ /* Enable clock lane and set up terminations */
+ val = 0;
+ if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
+ /* CSI2 */
+ unicam_set_field(&val, 1, UNICAM_CLE);
+ unicam_set_field(&val, 1, UNICAM_CLLPE);
+ if (unicam->bus_flags & V4L2_MBUS_CSI2_CONTINUOUS_CLOCK) {
+ unicam_set_field(&val, 1, UNICAM_CLTRE);
+ unicam_set_field(&val, 1, UNICAM_CLHSE);
+ }
+ } else {
+ /* CCP2 */
+ unicam_set_field(&val, 1, UNICAM_CLE);
+ unicam_set_field(&val, 1, UNICAM_CLHSE);
+ unicam_set_field(&val, 1, UNICAM_CLTRE);
+ }
+ unicam_reg_write(unicam, UNICAM_CLK, val);
+
+ /*
+ * Enable required data lanes with appropriate terminations.
+ * The same value needs to be written to UNICAM_DATn registers for
+ * the active lanes, and 0 for inactive ones.
+ */
+ val = 0;
+ if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
+ /* CSI2 */
+ unicam_set_field(&val, 1, UNICAM_DLE);
+ unicam_set_field(&val, 1, UNICAM_DLLPE);
+ if (unicam->bus_flags & V4L2_MBUS_CSI2_CONTINUOUS_CLOCK) {
+ unicam_set_field(&val, 1, UNICAM_DLTRE);
+ unicam_set_field(&val, 1, UNICAM_DLHSE);
+ }
+ } else {
+ /* CCP2 */
+ unicam_set_field(&val, 1, UNICAM_DLE);
+ unicam_set_field(&val, 1, UNICAM_DLHSE);
+ unicam_set_field(&val, 1, UNICAM_DLTRE);
+ }
+ unicam_reg_write(unicam, UNICAM_DAT0, val);
+
+ if (unicam->active_data_lanes == 1)
+ val = 0;
+ unicam_reg_write(unicam, UNICAM_DAT1, val);
+
+ if (unicam->max_data_lanes > 2) {
+ /*
+ * Registers UNICAM_DAT2 and UNICAM_DAT3 only valid if the
+ * instance supports more than 2 data lanes.
+ */
+ if (unicam->active_data_lanes == 2)
+ val = 0;
+ unicam_reg_write(unicam, UNICAM_DAT2, val);
+
+ if (unicam->active_data_lanes == 3)
+ val = 0;
+ unicam_reg_write(unicam, UNICAM_DAT3, val);
+ }
+
+ unicam_reg_write(unicam, UNICAM_IBLS,
+ node->v_fmt.fmt.pix.bytesperline);
+ size = node->v_fmt.fmt.pix.sizeimage;
+ unicam_wr_dma_addr(&unicam->node[UNICAM_IMAGE_NODE],
+ addr[UNICAM_IMAGE_NODE], size);
+ unicam_set_packing_config(unicam);
+ unicam_cfg_image_id(unicam);
+
+ val = unicam_reg_read(unicam, UNICAM_MISC);
+ unicam_set_field(&val, 1, UNICAM_FL0);
+ unicam_set_field(&val, 1, UNICAM_FL1);
+ unicam_reg_write(unicam, UNICAM_MISC, val);
+
+ /* Enable peripheral */
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPE);
+
+ /* Load image pointers */
+ unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_LIP_MASK);
+
+ /*
+ * Enable trigger only for the first frame to
+ * sync correctly to the FS from the source.
+ */
+ unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_TFC);
+}
+
+static void unicam_start_metadata(struct unicam_device *unicam, dma_addr_t *addr)
+{
+ struct unicam_node *node = &unicam->node[UNICAM_METADATA_NODE];
+ unsigned int size;
+
+ dev_dbg(unicam->dev, "enable metadata dma\n");
+ size = node->v_fmt.fmt.meta.buffersize;
+ unicam_enable_ed(unicam);
+ unicam_wr_dma_addr(node, addr[UNICAM_METADATA_NODE], size);
+ unicam_reg_write_field(unicam, UNICAM_DCS, 1, UNICAM_LDP);
+}
+
+static void unicam_disable(struct unicam_device *unicam)
+{
+ /* Analogue lane control disable */
+ unicam_reg_write_field(unicam, UNICAM_ANA, 1, UNICAM_DDL);
+
+ /* Stop the output engine */
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_SOE);
+
+ /* Disable the data lanes. */
+ unicam_reg_write(unicam, UNICAM_DAT0, 0);
+ unicam_reg_write(unicam, UNICAM_DAT1, 0);
+
+ if (unicam->max_data_lanes > 2) {
+ unicam_reg_write(unicam, UNICAM_DAT2, 0);
+ unicam_reg_write(unicam, UNICAM_DAT3, 0);
+ }
+
+ /* Peripheral reset */
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPR);
+ usleep_range(50, 100);
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPR);
+
+ /* Disable peripheral */
+ unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPE);
+
+ /* Clear ED setup */
+ unicam_reg_write(unicam, UNICAM_DCS, 0);
+
+ /* Disable all lane clocks */
+ unicam_clk_write(unicam, 0);
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 subdev Operations
+ */
+
+static int __unicam_subdev_set_routing(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_krouting *routing)
+{
+ int ret;
+
+ ret = v4l2_subdev_routing_validate_1_to_1(routing);
+ if (ret)
+ return ret;
+
+ v4l2_subdev_lock_state(state);
+
+ ret = v4l2_subdev_set_routing(sd, state, routing);
+
+ v4l2_subdev_unlock_state(state);
+
+ return ret;
+}
+
+static int unicam_subdev_set_routing(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ enum v4l2_subdev_format_whence which,
+ struct v4l2_subdev_krouting *routing)
+{
+ struct unicam_device *unicam = sd_to_unicam_device(sd);
+
+ if (which == V4L2_SUBDEV_FORMAT_ACTIVE && unicam->subdev.streaming)
+ return -EBUSY;
+
+ return __unicam_subdev_set_routing(sd, state, routing);
+}
+
+static int unicam_subdev_init_cfg(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state)
+{
+ struct v4l2_subdev_route routes[] = {
+ {
+ .sink_pad = UNICAM_SD_PAD_SINK,
+ .sink_stream = 0,
+ .source_pad = UNICAM_SD_PAD_SOURCE_IMAGE,
+ .source_stream = 0,
+ .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+ },
+ };
+
+ struct v4l2_subdev_krouting routing = {
+ .num_routes = ARRAY_SIZE(routes),
+ .routes = routes,
+ };
+
+ /* Initialize routing to single route to the fist source pad */
+ return __unicam_subdev_set_routing(sd, state, &routing);
+}
+
+static int unicam_subdev_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ u32 pad, stream;
+ int ret = -EINVAL;
+
+ v4l2_subdev_lock_state(state);
+
+ ret = v4l2_subdev_routing_find_opposite_end(&state->routing, code->pad,
+ code->stream, &pad,
+ &stream);
+ if (ret)
+ goto out;
+
+ if (unicam_sd_pad_is_source(code->pad)) {
+ /* No transcoding, source and sink codes must match. */
+ struct v4l2_mbus_framefmt *fmt;
+
+ fmt = v4l2_state_get_stream_format(state, pad, stream);
+ if (!fmt)
+ goto out;
+
+ if (code->index > 0)
+ goto out;
+
+ code->code = fmt->code;
+ } else {
+ const struct unicam_fmt *formats;
+ unsigned int num_formats;
+
+ if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
+ formats = unicam_image_formats;
+ num_formats = ARRAY_SIZE(unicam_image_formats);
+ } else {
+ formats = unicam_meta_formats;
+ num_formats = ARRAY_SIZE(unicam_meta_formats);
+ }
+
+ if (code->index >= num_formats)
+ goto out;
+
+ code->code = formats[code->index].code;
+ }
+
+out:
+ v4l2_subdev_unlock_state(state);
+
+ return ret;
+}
+
+static int unicam_sd_enable_streams(struct v4l2_subdev *sd, u32 pad,
+ u64 streams_mask)
+{
+ struct unicam_device *unicam = sd_to_unicam_device(sd);
+ struct v4l2_subdev_state *state;
+ u32 other_pad, other_stream;
+ int ret;
+
+ if (WARN_ON(streams_mask != 1))
+ return -EINVAL;
+
+ state = v4l2_subdev_lock_active_state(sd);
+
+ ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
+ &other_pad, &other_stream);
+
+ v4l2_subdev_unlock_state(state);
+
+ if (ret)
+ return ret;
+
+ unicam->sequence = 0;
+
+ dev_dbg(unicam->dev, "Running with %u data lanes\n",
+ unicam->active_data_lanes);
+
+ ret = clk_set_min_rate(unicam->vpu_clock, UNICAM_MIN_VPU_CLOCK_RATE);
+ if (ret) {
+ dev_err(unicam->dev, "failed to set up VPU clock\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(unicam->vpu_clock);
+ if (ret) {
+ dev_err(unicam->dev, "Failed to enable VPU clock: %d\n", ret);
+ goto err_vpu_clock;
+ }
+
+ ret = clk_set_rate(unicam->clock, 100 * 1000 * 1000);
+ if (ret) {
+ dev_err(unicam->dev, "failed to set up CSI clock\n");
+ goto err_vpu_prepare;
+ }
+
+ ret = clk_prepare_enable(unicam->clock);
+ if (ret) {
+ dev_err(unicam->dev, "Failed to enable CSI clock: %d\n", ret);
+ goto err_vpu_prepare;
+ }
+
+ ret = v4l2_subdev_call(unicam->sensor.subdev, video, enable_streams,
+ unicam->sensor.pad->index, BIT(other_stream));
+
+ if (ret && ret == -ENOIOCTLCMD)
+ ret = v4l2_subdev_call(unicam->sensor.subdev, video, s_stream, 1);
+
+ if (ret) {
+ dev_err(unicam->dev, "stream on failed in subdev\n");
+ goto err_enable_stream;
+ }
+
+ unicam->subdev.streaming = true;
+
+ return 0;
+
+err_enable_stream:
+ clk_disable_unprepare(unicam->clock);
+err_vpu_prepare:
+ clk_disable_unprepare(unicam->vpu_clock);
+err_vpu_clock:
+ if (clk_set_min_rate(unicam->vpu_clock, 0))
+ dev_err(unicam->dev, "failed to reset the VPU clock\n");
+ return ret;
+}
+
+static int unicam_sd_disable_streams(struct v4l2_subdev *sd, u32 pad,
+ u64 streams_mask)
+{
+ struct unicam_device *unicam = sd_to_unicam_device(sd);
+ struct v4l2_subdev_state *state;
+ u32 other_pad, other_stream;
+ int ret;
+
+ if (WARN_ON(streams_mask != 1))
+ return -EINVAL;
+
+ state = v4l2_subdev_lock_active_state(sd);
+
+ ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
+ &other_pad, &other_stream);
+
+ v4l2_subdev_unlock_state(state);
+
+ if (ret) {
+ dev_err(unicam->dev, "disable streams failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = v4l2_subdev_call(unicam->sensor.subdev, video, disable_streams,
+ unicam->sensor.pad->index, BIT(other_stream));
+
+ if (ret && ret == -ENOIOCTLCMD) {
+ /* The sensor does not support disable streams. */
+ if (unicam->node[UNICAM_IMAGE_NODE].streaming &&
+ other_pad == unicam->node[UNICAM_METADATA_NODE].pad.index) {
+ /* We won't stop the sensor yet. */
+ unicam->subdev.streaming = false;
+ return 0;
+ }
+ /* There is no more the metadata node, we can stop. */
+ ret = v4l2_subdev_call(unicam->sensor.subdev, video, s_stream, 0);
+ }
+
+ clk_disable_unprepare(unicam->clock);
+ if (clk_set_min_rate(unicam->vpu_clock, 0))
+ dev_err(unicam->dev, "failed to reset the VPU clock\n");
+ clk_disable_unprepare(unicam->vpu_clock);
+
+ unicam->subdev.streaming = false;
+
+ return 0;
+}
+
+static int unicam_subdev_set_pad_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_format *format)
+{
+ struct unicam_device *unicam = sd_to_unicam_device(sd);
+ struct v4l2_mbus_framefmt *sink_format, *source_format;
+ const struct unicam_fmt *fmtinfo;
+ int ret = 0;
+
+ if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && unicam->subdev.streaming)
+ return -EBUSY;
+
+ /* No transcoding, source and sink formats must match. */
+ if (unicam_sd_pad_is_source(format->pad))
+ return v4l2_subdev_get_fmt(sd, state, format);
+
+ fmtinfo = unicam_find_format_by_code(format->format.code, format->pad);
+ if (!fmtinfo)
+ fmtinfo = &unicam_image_formats[0];
+
+ if (format->pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
+ format->format.width = clamp_t(unsigned int,
+ format->format.width,
+ UNICAM_MIN_WIDTH,
+ UNICAM_MAX_WIDTH);
+ format->format.height = clamp_t(unsigned int,
+ format->format.height,
+ UNICAM_MIN_HEIGHT,
+ UNICAM_MAX_HEIGHT);
+ }
+ format->format.field = V4L2_FIELD_NONE;
+
+ v4l2_subdev_lock_state(state);
+
+ sink_format = v4l2_state_get_stream_format(state, format->pad,
+ format->stream);
+ source_format = v4l2_subdev_state_get_opposite_stream_format(state,
+ format->pad,
+ format->stream);
+ if (!sink_format || !source_format) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ *sink_format = format->format;
+ *source_format = format->format;
+out:
+ v4l2_subdev_unlock_state(state);
+
+ return ret;
+}
+
+static int unicam_subdev_enum_frame_size(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ const struct unicam_fmt *fmtinfo;
+ u32 pad, stream;
+ int ret = -EINVAL;
+
+ if (fse->index > 0)
+ return ret;
+
+ v4l2_subdev_lock_state(state);
+
+ ret = v4l2_subdev_routing_find_opposite_end(&state->routing, fse->pad,
+ fse->stream, &pad,
+ &stream);
+ if (ret)
+ goto out;
+
+ if (unicam_sd_pad_is_source(fse->pad)) {
+ /* No transcoding, source and sink formats must match. */
+ struct v4l2_mbus_framefmt *fmt;
+
+ fmt = v4l2_state_get_stream_format(state, pad, stream);
+ if (!fmt)
+ goto out;
+
+ if (fse->code != fmt->code)
+ goto out;
+
+ fse->min_width = fmt->width;
+ fse->max_width = fmt->width;
+ fse->min_height = fmt->height;
+ fse->max_height = fmt->height;
+ } else {
+ fmtinfo = unicam_find_format_by_code(fse->code, pad);
+ if (!fmtinfo)
+ goto out;
+
+ fse->min_width = UNICAM_MIN_WIDTH;
+ fse->max_width = UNICAM_MAX_WIDTH;
+ fse->min_height = UNICAM_MIN_HEIGHT;
+ fse->max_height = UNICAM_MAX_HEIGHT;
+ }
+
+out:
+ v4l2_subdev_unlock_state(state);
+
+ return ret;
+}
+
+static const struct v4l2_subdev_video_ops unicam_subdev_video_ops = {
+ .enable_streams = unicam_sd_enable_streams,
+ .disable_streams = unicam_sd_disable_streams,
+};
+
+static const struct v4l2_subdev_pad_ops unicam_subdev_pad_ops = {
+ .init_cfg = unicam_subdev_init_cfg,
+ .enum_mbus_code = unicam_subdev_enum_mbus_code,
+ .get_fmt = v4l2_subdev_get_fmt,
+ .set_fmt = unicam_subdev_set_pad_format,
+ .set_routing = unicam_subdev_set_routing,
+ .enum_frame_size = unicam_subdev_enum_frame_size,
+};
+
+static const struct v4l2_subdev_ops unicam_subdev_ops = {
+ .video = &unicam_subdev_video_ops,
+ .pad = &unicam_subdev_pad_ops,
+};
+
+static const struct media_entity_operations unicam_subdev_media_ops = {
+ .link_validate = v4l2_subdev_link_validate,
+ .has_route = v4l2_subdev_has_route,
+};
+
+static int unicam_init_and_register_subdev(struct unicam_device *unicam)
+{
+ struct v4l2_subdev *sd = &unicam->subdev.sd;
+ int ret;
+
+ v4l2_subdev_init(sd, &unicam_subdev_ops);
+ v4l2_set_subdevdata(sd, unicam);
+ unicam->subdev.sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+ unicam->subdev.sd.entity.ops = &unicam_subdev_media_ops;
+ unicam->subdev.sd.dev = unicam->dev;
+ unicam->subdev.sd.owner = THIS_MODULE;
+ unicam->subdev.sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_MULTIPLEXED;
+ snprintf(unicam->subdev.sd.name, sizeof(unicam->subdev.sd.name), "unicam-subdev");
+
+ unicam->subdev.pads[UNICAM_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+
+ unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_IMAGE].flags = MEDIA_PAD_FL_SOURCE;
+ unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_METADATA].flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&sd->entity,
+ ARRAY_SIZE(unicam->subdev.pads), unicam->subdev.pads);
+ if (ret) {
+ dev_err(unicam->dev, "Could not init media entity");
+ return ret;
+ }
+
+ ret = v4l2_subdev_init_finalize(sd);
+ if (ret) {
+ dev_err(unicam->dev, "Could not finalize init for subdev");
+ return ret;
+ }
+
+ return v4l2_device_register_subdev(&unicam->v4l2_dev, sd);
+}
+
+/* -----------------------------------------------------------------------------
+ * Videobuf2 Queue Operations
+ */
+
+static int unicam_queue_setup(struct vb2_queue *vq,
+ unsigned int *nbuffers,
+ unsigned int *nplanes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct unicam_node *node = vb2_get_drv_priv(vq);
+ struct unicam_device *unicam = node->dev;
+ unsigned int size = is_image_node(node) ?
+ node->v_fmt.fmt.pix.sizeimage :
+ node->v_fmt.fmt.meta.buffersize;
+
+ if (vq->num_buffers + *nbuffers < 3)
+ *nbuffers = 3 - vq->num_buffers;
+
+ if (*nplanes) {
+ if (sizes[0] < size) {
+ dev_dbg(unicam->dev, "sizes[0] %i < size %u\n", sizes[0],
+ size);
+ return -EINVAL;
+ }
+ size = sizes[0];
+ }
+
+ *nplanes = 1;
+ sizes[0] = size;
+
+ return 0;
+}
+
+static int unicam_buffer_prepare(struct vb2_buffer *vb)
+{
+ struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
+ struct unicam_device *unicam = node->dev;
+ struct unicam_buffer *buf = to_unicam_buffer(vb);
+ unsigned long size;
+
+ if (WARN_ON(!node->fmt))
+ return -EINVAL;
+
+ size = is_image_node(node) ? node->v_fmt.fmt.pix.sizeimage :
+ node->v_fmt.fmt.meta.buffersize;
+ if (vb2_plane_size(vb, 0) < size) {
+ dev_dbg(unicam->dev, "data will not fit into plane (%lu < %lu)\n",
+ vb2_plane_size(vb, 0), size);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
+ return 0;
+}
+
+static void unicam_buffer_queue(struct vb2_buffer *vb)
+{
+ struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
+ struct unicam_buffer *buf = to_unicam_buffer(vb);
+
+ spin_lock_irq(&node->dma_queue_lock);
+ list_add_tail(&buf->list, &node->dma_queue);
+ spin_unlock_irq(&node->dma_queue_lock);
+}
+
+static void unicam_return_buffers(struct unicam_node *node,
+ enum vb2_buffer_state state)
+{
+ struct unicam_buffer *buf, *tmp;
+
+ list_for_each_entry_safe(buf, tmp, &node->dma_queue, list) {
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->vb.vb2_buf, state);
+ }
+
+ if (node->cur_frm)
+ vb2_buffer_done(&node->cur_frm->vb.vb2_buf,
+ state);
+ if (node->next_frm && node->cur_frm != node->next_frm)
+ vb2_buffer_done(&node->next_frm->vb.vb2_buf,
+ state);
+
+ node->cur_frm = NULL;
+ node->next_frm = NULL;
+}
+
+static int unicam_video_check_format(struct unicam_node *node)
+{
+ const struct v4l2_mbus_framefmt *format;
+ struct unicam_device *unicam = node->dev;
+ struct v4l2_subdev_state *state;
+ int ret = 0;
+
+ state = v4l2_subdev_lock_active_state(&unicam->subdev.sd);
+
+ format = v4l2_state_get_stream_format(state, UNICAM_SD_PAD_SOURCE_IMAGE, 0);
+ if (!format) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (node->fmt->code != format->code ||
+ node->v_fmt.fmt.pix.height != format->height ||
+ node->v_fmt.fmt.pix.width != format->width ||
+ node->v_fmt.fmt.pix.field != format->field) {
+ dev_dbg(unicam->dev, "(%u x %u) %08x %s != (%u x %u) %08x %s\n",
+ node->v_fmt.fmt.pix.width, node->v_fmt.fmt.pix.height,
+ node->fmt->code,
+ v4l2_field_names[node->v_fmt.fmt.pix.field],
+ format->width, format->height, format->code,
+ v4l2_field_names[format->field]);
+ ret = -EPIPE;
+ goto out;
+ }
+
+out:
+ v4l2_subdev_unlock_state(state);
+
+ return ret;
+}
+
+static int unicam_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct unicam_node *node = vb2_get_drv_priv(vq);
+ struct unicam_device *unicam = node->dev;
+ dma_addr_t buffer_addr[UNICAM_MAX_NODES] = { 0 };
+ struct unicam_buffer *buf;
+ struct v4l2_subdev_state *state;
+ unsigned long flags;
+ unsigned int i;
+ int ret;
+ u32 pad, stream;
+ u32 remote_pad_index = is_image_node(node) ? UNICAM_SD_PAD_SOURCE_IMAGE
+ : UNICAM_SD_PAD_SOURCE_METADATA;
+
+ /* Look for the route for the given pad and stream. */
+ state = v4l2_subdev_lock_active_state(&unicam->subdev.sd);
+
+ ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
+ remote_pad_index, 0,
+ &pad, &stream);
+ if (ret)
+ goto err_streaming;
+
+ v4l2_subdev_unlock_state(state);
+
+ dev_dbg(unicam->dev, "Starting stream 0 on pad %d on subdev %s\n",
+ remote_pad_index, unicam->subdev.sd.name);
+
+ /* The metadata node can't be started alone. */
+ if (is_metadata_node(node)) {
+ if (!unicam->node[UNICAM_IMAGE_NODE].streaming) {
+ dev_err(unicam->dev,
+ "Can't start metadata without image\n");
+ return -EINVAL;
+ }
+ dev_dbg(unicam->dev, "starting metadata node\n");
+
+ spin_lock_irqsave(&node->dma_queue_lock, flags);
+ buf = list_first_entry(&node->dma_queue,
+ struct unicam_buffer, list);
+ dev_dbg(unicam->dev, "buffer %d: %p\n", i, buf);
+ node->cur_frm = buf;
+ node->next_frm = buf;
+ list_del(&buf->list);
+ spin_unlock_irqrestore(&node->dma_queue_lock, flags);
+
+ buffer_addr[UNICAM_METADATA_NODE] =
+ vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
+ dev_dbg(unicam->dev, "buffer %d addr: %lld\n", i, buffer_addr[i]);
+
+ unicam_start_metadata(unicam, buffer_addr);
+ node->streaming = true;
+ return 0;
+ }
+
+ ret = pm_runtime_get_sync(unicam->dev);
+ if (ret < 0) {
+ dev_err(unicam->dev, "pm_runtime_get_sync failed\n");
+ goto err_streaming;
+ }
+
+ ret = media_pipeline_start(node->video_dev.entity.pads, &unicam->pipe);
+ if (ret < 0) {
+ dev_dbg(unicam->dev, "Failed to start media pipeline: %d\n", ret);
+ goto err_pm_put;
+ }
+
+ ret = unicam_video_check_format(node);
+ if (ret < 0) {
+ dev_err(unicam->dev, "Video format is incorrect: %d\n", ret);
+ goto error_pipeline;
+ }
+
+ dev_dbg(unicam->dev, "node %s\n", node->video_dev.name);
+
+ spin_lock_irqsave(&node->dma_queue_lock, flags);
+ buf = list_first_entry(&node->dma_queue,
+ struct unicam_buffer, list);
+ dev_dbg(unicam->dev, "buffer %d: %p\n", i, buf);
+ node->cur_frm = buf;
+ node->next_frm = buf;
+ list_del(&buf->list);
+ spin_unlock_irqrestore(&node->dma_queue_lock, flags);
+
+ buffer_addr[UNICAM_IMAGE_NODE] =
+ vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
+
+ dev_dbg(unicam->dev, "Start unicam\n");
+ unicam_start_rx(unicam, buffer_addr);
+
+ dev_dbg(unicam->dev, "Enable stream\n");
+ ret = v4l2_subdev_call(&unicam->subdev.sd, video, enable_streams,
+ remote_pad_index, BIT(0));
+ if (ret < 0) {
+ dev_err(unicam->dev, "stream on failed in subdev\n");
+ goto error_pipeline;
+ }
+
+ node->streaming = true;
+
+ return 0;
+
+error_pipeline:
+ media_pipeline_stop(node->video_dev.entity.pads);
+err_pm_put:
+ pm_runtime_put_sync(unicam->dev);
+err_streaming:
+ v4l2_subdev_unlock_state(state);
+ return ret;
+}
+
+static void unicam_stop_streaming(struct vb2_queue *vq)
+{
+ struct unicam_node *node = vb2_get_drv_priv(vq);
+ struct unicam_device *unicam = node->dev;
+ u32 remote_pad_index = is_image_node(node) ? UNICAM_SD_PAD_SOURCE_IMAGE
+ : UNICAM_SD_PAD_SOURCE_METADATA;
+
+ node->streaming = false;
+
+ v4l2_subdev_call(&unicam->subdev.sd, video, disable_streams,
+ remote_pad_index, BIT(0));
+
+ /* We can stream only with the image node. */
+ if (is_metadata_node(node)) {
+ /*
+ * Allow the hardware to spin in the dummy buffer.
+ * This is only really needed if the embedded data pad is
+ * disabled before the image pad.
+ */
+ unicam_wr_dma_addr(node, node->dummy_buf_dma_addr,
+ UNICAM_DUMMY_BUF_SIZE);
+ goto dequeue_buffers;
+ }
+
+ unicam_disable(unicam);
+
+ media_pipeline_stop(node->video_dev.entity.pads);
+ pm_runtime_put(unicam->dev);
+
+dequeue_buffers:
+ /* Clear all queued buffers for the node */
+ unicam_return_buffers(node, VB2_BUF_STATE_ERROR);
+}
+
+static const struct vb2_ops unicam_video_qops = {
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .queue_setup = unicam_queue_setup,
+ .buf_prepare = unicam_buffer_prepare,
+ .buf_queue = unicam_buffer_queue,
+ .start_streaming = unicam_start_streaming,
+ .stop_streaming = unicam_stop_streaming,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 video device Operations
+ */
+
+static int unicam_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct unicam_node *node = video_drvdata(file);
+ struct unicam_device *unicam = node->dev;
+
+ strscpy(cap->driver, UNICAM_MODULE_NAME, sizeof(cap->driver));
+ strscpy(cap->card, UNICAM_MODULE_NAME, sizeof(cap->card));
+
+ snprintf(cap->bus_info, sizeof(cap->bus_info),
+ "platform:%s", dev_name(unicam->dev));
+
+ cap->capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE;
+
+ return 0;
+}
+
+static int unicam_log_status(struct file *file, void *fh)
+{
+ struct unicam_node *node = video_drvdata(file);
+ struct unicam_device *unicam = node->dev;
+ u32 reg;
+
+ /* status for sub devices */
+ v4l2_device_call_all(&unicam->v4l2_dev, 0, core, log_status);
+
+ dev_info(unicam->dev, "-----Receiver status-----\n");
+ dev_info(unicam->dev, "V4L2 width/height: %ux%u\n",
+ node->v_fmt.fmt.pix.width, node->v_fmt.fmt.pix.height);
+ dev_info(unicam->dev, "Mediabus format: %08x\n", node->fmt->code);
+ dev_info(unicam->dev, "V4L2 format: %08x\n",
+ node->v_fmt.fmt.pix.pixelformat);
+ reg = unicam_reg_read(unicam, UNICAM_IPIPE);
+ dev_info(unicam->dev, "Unpacking/packing: %u / %u\n",
+ unicam_get_field(reg, UNICAM_PUM_MASK),
+ unicam_get_field(reg, UNICAM_PPM_MASK));
+ dev_info(unicam->dev, "----Live data----\n");
+ dev_info(unicam->dev, "Programmed stride: %4u\n",
+ unicam_reg_read(unicam, UNICAM_IBLS));
+ dev_info(unicam->dev, "Detected resolution: %ux%u\n",
+ unicam_reg_read(unicam, UNICAM_IHSTA),
+ unicam_reg_read(unicam, UNICAM_IVSTA));
+ dev_info(unicam->dev, "Write pointer: %08x\n",
+ unicam_reg_read(unicam, UNICAM_IBWP));
+
+ return 0;
+}
+
+static int unicam_subscribe_event(struct v4l2_fh *fh,
+ const struct v4l2_event_subscription *sub)
+{
+ switch (sub->type) {
+ case V4L2_EVENT_FRAME_SYNC:
+ return v4l2_event_subscribe(fh, sub, 2, NULL);
+ }
+
+ return v4l2_ctrl_subscribe_event(fh, sub);
+}
+
+static int unicam_enum_fmt_vid(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ unsigned int i, index;
+
+ for (i = 0, index = 0; i < ARRAY_SIZE(unicam_image_formats); i++) {
+ if (f->mbus_code && unicam_image_formats[i].code != f->mbus_code)
+ continue;
+
+ if (index == f->index) {
+ f->pixelformat = unicam_image_formats[i].fourcc;
+ return 0;
+ }
+
+ index++;
+
+ if (unicam_image_formats[i].unpacked_fourcc) {
+ if (index == f->index) {
+ f->pixelformat = unicam_image_formats[i].unpacked_fourcc;
+ return 0;
+ }
+ index++;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int unicam_g_fmt_vid(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct unicam_node *node = video_drvdata(file);
+
+ *f = node->v_fmt;
+
+ return 0;
+}
+
+static const struct unicam_fmt *
+unicam_try_fmt(struct unicam_node *node, struct v4l2_format *f)
+{
+ struct v4l2_pix_format *v4l2_format = &f->fmt.pix;
+ struct unicam_device *unicam = node->dev;
+ const struct unicam_fmt *fmt;
+
+ /*
+ * Default to the first format if the requested pixel format code isn't
+ * supported.
+ */
+ fmt = unicam_find_format_by_fourcc(v4l2_format->pixelformat,
+ UNICAM_SD_PAD_SOURCE_IMAGE);
+ if (!fmt) {
+ fmt = &unicam_image_formats[0];
+ v4l2_format->pixelformat = fmt->fourcc;
+ }
+
+ unicam_calc_format_size_bpl(unicam, fmt, f);
+
+ if (v4l2_format->field == V4L2_FIELD_ANY)
+ v4l2_format->field = V4L2_FIELD_NONE;
+
+ dev_dbg(unicam->dev, "%s: %08x %ux%u (bytesperline %u sizeimage %u)\n",
+ __func__, v4l2_format->pixelformat,
+ v4l2_format->width, v4l2_format->height,
+ v4l2_format->bytesperline, v4l2_format->sizeimage);
+
+ return fmt;
+}
+
+static int unicam_try_fmt_vid(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct unicam_node *node = video_drvdata(file);
+
+ unicam_try_fmt(node, f);
+ return 0;
+}
+
+static int unicam_s_fmt_vid(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct unicam_node *node = video_drvdata(file);
+ struct unicam_device *unicam = node->dev;
+
+ if (vb2_is_busy(&node->buffer_queue)) {
+ dev_dbg(unicam->dev, "%s device busy\n", __func__);
+ return -EBUSY;
+ }
+
+ node->fmt = unicam_try_fmt(node, f);
+
+ node->v_fmt = *f;
+
+ return 0;
+}
+
+static int unicam_enum_framesizes(struct file *file, void *fh,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct unicam_node *node = video_drvdata(file);
+ int ret = -EINVAL;
+
+ if (fsize->index > 0)
+ return ret;
+
+ if (is_image_node(node)) {
+ if (!unicam_find_format_by_fourcc(fsize->pixel_format,
+ UNICAM_SD_PAD_SOURCE_IMAGE))
+ return ret;
+
+ fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+ fsize->stepwise.min_width = UNICAM_MIN_WIDTH;
+ fsize->stepwise.max_width = UNICAM_MAX_WIDTH;
+ fsize->stepwise.step_width = 1;
+ fsize->stepwise.min_height = UNICAM_MIN_HEIGHT;
+ fsize->stepwise.max_height = UNICAM_MAX_HEIGHT;
+ fsize->stepwise.step_height = 1;
+ } else {
+ if (!unicam_find_format_by_fourcc(fsize->pixel_format,
+ UNICAM_SD_PAD_SOURCE_METADATA))
+ return ret;
+
+ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ fsize->discrete.width = UNICAM_EMBEDDED_SIZE;
+ fsize->discrete.height = 1;
+ }
+
+ return 0;
+}
+
+static int unicam_enum_fmt_meta(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ unsigned int i, index;
+
+ for (i = 0, index = 0; i < ARRAY_SIZE(unicam_meta_formats); i++) {
+ if (f->mbus_code && unicam_meta_formats[i].code != f->mbus_code)
+ continue;
+ if (!unicam_meta_formats[i].metadata_fmt)
+ continue;
+
+ if (index == f->index) {
+ f->pixelformat = unicam_meta_formats[i].fourcc;
+ f->type = V4L2_BUF_TYPE_META_CAPTURE;
+ return 0;
+ }
+ index++;
+ }
+
+ return -EINVAL;
+}
+
+static int unicam_g_fmt_meta(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct unicam_node *node = video_drvdata(file);
+
+ f->fmt.meta = node->v_fmt.fmt.meta;
+
+ return 0;
+}
+
+static int unicam_try_fmt_meta(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ f->fmt.meta.dataformat = V4L2_META_FMT_8;
+ f->fmt.meta.buffersize = UNICAM_EMBEDDED_SIZE;
+
+ return 0;
+}
+
+static int unicam_s_fmt_meta(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct unicam_node *node = video_drvdata(file);
+
+ unicam_try_fmt_meta(file, priv, f);
+
+ node->v_fmt = *f;
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops unicam_ioctl_ops = {
+ .vidioc_querycap = unicam_querycap,
+ .vidioc_enum_fmt_vid_cap = unicam_enum_fmt_vid,
+ .vidioc_g_fmt_vid_cap = unicam_g_fmt_vid,
+ .vidioc_try_fmt_vid_cap = unicam_try_fmt_vid,
+ .vidioc_s_fmt_vid_cap = unicam_s_fmt_vid,
+
+ .vidioc_enum_fmt_meta_cap = unicam_enum_fmt_meta,
+ .vidioc_g_fmt_meta_cap = unicam_g_fmt_meta,
+ .vidioc_try_fmt_meta_cap = unicam_try_fmt_meta,
+ .vidioc_s_fmt_meta_cap = unicam_s_fmt_meta,
+
+ .vidioc_enum_framesizes = unicam_enum_framesizes,
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_streamon = vb2_ioctl_streamon,
+ .vidioc_streamoff = vb2_ioctl_streamoff,
+
+ .vidioc_log_status = unicam_log_status,
+ .vidioc_subscribe_event = unicam_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+/* unicam capture driver file operations */
+static const struct v4l2_file_operations unicam_fops = {
+ .owner = THIS_MODULE,
+ .open = v4l2_fh_open,
+ .release = vb2_fop_release,
+ .poll = vb2_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+};
+
+static void unicam_set_default_format(struct unicam_node *node)
+{
+ if (is_image_node(node)) {
+ struct v4l2_pix_format *pix_fmt = &node->v_fmt.fmt.pix;
+
+ pix_fmt->width = default_image_format.width;
+ pix_fmt->height = default_image_format.height;
+ pix_fmt->field = default_image_format.field;
+ pix_fmt->colorspace = default_image_format.colorspace;
+ pix_fmt->ycbcr_enc = default_image_format.ycbcr_enc;
+ pix_fmt->quantization = default_image_format.quantization;
+ pix_fmt->xfer_func = default_image_format.xfer_func;
+ pix_fmt->pixelformat = unicam_image_formats[0].fourcc;
+ unicam_calc_format_size_bpl(node->dev, &unicam_image_formats[0],
+ &node->v_fmt);
+ node->v_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+ node->fmt = &unicam_image_formats[0];
+ } else {
+ const struct unicam_fmt *fmt;
+
+ /* Fix this node format as embedded data. */
+ fmt = &unicam_meta_formats[0];
+ node->fmt = fmt;
+ node->v_fmt.fmt.meta.dataformat = fmt->fourcc;
+ node->v_fmt.fmt.meta.buffersize = UNICAM_EMBEDDED_SIZE;
+ node->v_fmt.type = V4L2_BUF_TYPE_META_CAPTURE;
+ }
+}
+
+static int unicam_register_node(struct unicam_device *unicam,
+ enum unicam_node_type type)
+{
+ struct video_device *vdev;
+ struct vb2_queue *q;
+ struct unicam_node *node = &unicam->node[type];
+ int ret;
+ u32 pad_index = type == UNICAM_IMAGE_NODE ? UNICAM_SD_PAD_SOURCE_IMAGE
+ : UNICAM_SD_PAD_SOURCE_METADATA;
+
+ node->dev = unicam_get(unicam);
+ node->id = type;
+
+ spin_lock_init(&node->dma_queue_lock);
+ mutex_init(&node->lock);
+
+ INIT_LIST_HEAD(&node->dma_queue);
+
+ /* Initialize the videobuf2 queue. */
+ q = &node->buffer_queue;
+ q->type = type == UNICAM_IMAGE_NODE ? V4L2_BUF_TYPE_VIDEO_CAPTURE
+ : V4L2_BUF_TYPE_META_CAPTURE;
+ q->io_modes = VB2_MMAP | VB2_DMABUF;
+ q->drv_priv = node;
+ q->ops = &unicam_video_qops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->buf_struct_size = sizeof(struct unicam_buffer);
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->lock = &node->lock;
+ q->min_buffers_needed = 1;
+ q->dev = unicam->dev;
+
+ ret = vb2_queue_init(q);
+ if (ret) {
+ dev_err(unicam->dev, "vb2_queue_init() failed\n");
+ goto err_unicam_put;
+ }
+
+ /* Initialize the video device. */
+ vdev = &node->video_dev;
+ vdev->release = unicam_node_release;
+ vdev->fops = &unicam_fops;
+ vdev->ioctl_ops = &unicam_ioctl_ops;
+ vdev->v4l2_dev = &unicam->v4l2_dev;
+ vdev->vfl_dir = VFL_DIR_RX;
+ vdev->queue = q;
+ vdev->lock = &node->lock;
+ vdev->device_caps = type == UNICAM_IMAGE_NODE ?
+ V4L2_CAP_VIDEO_CAPTURE : V4L2_CAP_META_CAPTURE;
+ vdev->device_caps |= V4L2_CAP_STREAMING | V4L2_CAP_IO_MC;
+
+ /* Define the device names */
+ snprintf(vdev->name, sizeof(vdev->name), "%s-%s", UNICAM_MODULE_NAME,
+ type == UNICAM_IMAGE_NODE ? "image" : "embedded");
+
+ video_set_drvdata(vdev, node);
+ if (type == UNICAM_IMAGE_NODE)
+ vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT;
+ node->pad.flags = MEDIA_PAD_FL_SINK;
+ ret = media_entity_pads_init(&vdev->entity, 1, &node->pad);
+ if (ret)
+ goto err_unicam_put;
+
+ node->dummy_buf_cpu_addr = dma_alloc_coherent(unicam->dev,
+ UNICAM_DUMMY_BUF_SIZE,
+ &node->dummy_buf_dma_addr,
+ GFP_KERNEL);
+ if (!node->dummy_buf_cpu_addr) {
+ dev_err(unicam->dev, "Unable to allocate dummy buffer.\n");
+ ret = -ENOMEM;
+ goto err_entity_cleanup;
+ }
+
+ unicam_set_default_format(node);
+
+ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+ if (ret) {
+ dev_err(unicam->dev, "Unable to register video device %s\n",
+ vdev->name);
+ goto err_dma_free;
+ }
+
+ node->registered = true;
+
+ ret = media_create_pad_link(&unicam->subdev.sd.entity,
+ pad_index,
+ &node->video_dev.entity,
+ 0,
+ MEDIA_LNK_FL_ENABLED |
+ MEDIA_LNK_FL_IMMUTABLE);
+ if (ret) {
+ /*
+ * No need for cleanup, the caller will unregister the
+ * video device, which will drop the reference on the
+ * device and trigger the cleanup.
+ */
+ dev_err(unicam->dev, "Unable to create pad link for %s",
+ unicam->sensor.subdev->name);
+ return ret;
+ }
+
+ return 0;
+
+err_dma_free:
+ dma_free_coherent(unicam->dev, UNICAM_DUMMY_BUF_SIZE,
+ node->dummy_buf_cpu_addr,
+ node->dummy_buf_dma_addr);
+err_entity_cleanup:
+ media_entity_cleanup(&vdev->entity);
+err_unicam_put:
+ unicam_put(unicam);
+ return ret;
+}
+
+static void unicam_unregister_nodes(struct unicam_device *unicam)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
+ struct unicam_node *node = &unicam->node[i];
+
+ if (node->dummy_buf_cpu_addr)
+ dma_free_coherent(unicam->dev, UNICAM_DUMMY_BUF_SIZE,
+ node->dummy_buf_cpu_addr,
+ node->dummy_buf_dma_addr);
+
+ if (node->registered) {
+ video_unregister_device(&node->video_dev);
+ node->registered = false;
+ }
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 async notifier
+ */
+
+static int
+unicam_async_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct unicam_device *unicam = notifier_to_unicam_device(notifier);
+ struct media_pad *sink = &unicam->subdev.sd.entity.pads[UNICAM_SD_PAD_SINK];
+
+ unicam->sensor.subdev = subdev;
+ dev_dbg(unicam->dev, "Using sensor %s for capture\n",
+ subdev->name);
+
+ unicam->sensor.pad = sink;
+
+ return v4l2_create_fwnode_links_to_pad(subdev, sink, MEDIA_LNK_FL_ENABLED |
+ MEDIA_LNK_FL_IMMUTABLE);
+}
+
+static int unicam_async_complete(struct v4l2_async_notifier *notifier)
+{
+ struct unicam_device *unicam = notifier_to_unicam_device(notifier);
+ int ret;
+
+ ret = unicam_register_node(unicam, UNICAM_IMAGE_NODE);
+ if (ret) {
+ dev_err(unicam->dev, "Unable to register image video device.\n");
+ goto unregister;
+ }
+
+ ret = unicam_register_node(unicam, UNICAM_METADATA_NODE);
+ if (ret) {
+ dev_err(unicam->dev, "Unable to register metadata video device.\n");
+ goto unregister;
+ }
+
+ ret = v4l2_device_register_subdev_nodes(&unicam->v4l2_dev);
+ if (ret) {
+ dev_err(unicam->dev, "Unable to register subdev nodes.\n");
+ goto unregister;
+ }
+
+ return 0;
+
+unregister:
+ unicam_unregister_nodes(unicam);
+ unicam_put(unicam);
+
+ return ret;
+}
+
+static const struct v4l2_async_notifier_operations unicam_async_ops = {
+ .bound = unicam_async_bound,
+ .complete = unicam_async_complete,
+};
+
+static int unicam_connect_of_subdevs(struct unicam_device *unicam)
+{
+ struct v4l2_fwnode_endpoint ep = { };
+ struct fwnode_handle *ep_handle;
+ struct v4l2_async_subdev *asd;
+ unsigned int lane;
+ int ret = -EINVAL;
+
+ if (of_property_read_u32(unicam->dev->of_node, "brcm,num-data-lanes",
+ &unicam->max_data_lanes) < 0) {
+ dev_err(unicam->dev, "DT property %s not set\n",
+ "brcm,num-data-lanes");
+ return -EINVAL;
+ }
+
+ /* Get the local endpoint and remote device. */
+ ep_handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(unicam->dev),
+ 0, 0,
+ FWNODE_GRAPH_ENDPOINT_NEXT);
+ if (!ep_handle) {
+ dev_err(unicam->dev, "No endpoint\n");
+ return -ENODEV;
+ }
+
+ /* Parse the local endpoint and validate its configuration. */
+ if (v4l2_fwnode_endpoint_alloc_parse(ep_handle, &ep)) {
+ dev_err(unicam->dev, "could not parse endpoint\n");
+ goto cleanup_exit;
+ }
+
+ dev_dbg(unicam->dev, "parsed local endpoint, bus_type %u\n",
+ ep.bus_type);
+
+ unicam->bus_type = ep.bus_type;
+
+ switch (ep.bus_type) {
+ case V4L2_MBUS_CSI2_DPHY:
+ switch (ep.bus.mipi_csi2.num_data_lanes) {
+ case 1:
+ case 2:
+ case 4:
+ break;
+
+ default:
+ dev_err(unicam->dev, "%u data lanes not supported\n",
+ ep.bus.mipi_csi2.num_data_lanes);
+ goto cleanup_exit;
+ }
+
+ for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
+ if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
+ dev_err(unicam->dev, "data lanes reordering not supported\n");
+ goto cleanup_exit;
+ }
+ }
+
+ if (ep.bus.mipi_csi2.num_data_lanes > unicam->max_data_lanes) {
+ dev_err(unicam->dev, "endpoint requires %u data lanes when %u are supported\n",
+ ep.bus.mipi_csi2.num_data_lanes,
+ unicam->max_data_lanes);
+ }
+
+ unicam->active_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
+ unicam->bus_flags = ep.bus.mipi_csi2.flags;
+
+ break;
+
+ case V4L2_MBUS_CCP2:
+ if (ep.bus.mipi_csi1.clock_lane != 0 ||
+ ep.bus.mipi_csi1.data_lane != 1) {
+ dev_err(unicam->dev, "unsupported lanes configuration\n");
+ goto cleanup_exit;
+ }
+
+ unicam->max_data_lanes = 1;
+ unicam->active_data_lanes = 1;
+ unicam->bus_flags = ep.bus.mipi_csi1.strobe;
+ break;
+
+ default:
+ /* Unsupported bus type */
+ dev_err(unicam->dev, "unsupported bus type %u\n",
+ ep.bus_type);
+ goto cleanup_exit;
+ }
+
+ dev_dbg(unicam->dev, "%s bus, %u data lanes, flags=0x%08x\n",
+ unicam->bus_type == V4L2_MBUS_CSI2_DPHY ? "CSI-2" : "CCP2",
+ unicam->active_data_lanes, unicam->bus_flags);
+
+ /* Initialize and register the async notifier. */
+ v4l2_async_nf_init(&unicam->notifier);
+
+ asd = v4l2_async_nf_add_fwnode_remote(&unicam->notifier, ep_handle,
+ struct v4l2_async_subdev);
+
+ fwnode_handle_put(ep_handle);
+ ep_handle = NULL;
+
+ if (IS_ERR(asd)) {
+ ret = PTR_ERR(asd);
+ goto cleanup_exit;
+ }
+
+ unicam->notifier.ops = &unicam_async_ops;
+
+ ret = v4l2_async_nf_register(&unicam->v4l2_dev, &unicam->notifier);
+ if (ret) {
+ dev_err(unicam->dev, "Error registering device notifier: %d\n", ret);
+ goto cleanup_exit;
+ }
+
+ return 0;
+
+cleanup_exit:
+ v4l2_fwnode_endpoint_free(&ep);
+ fwnode_handle_put(ep_handle);
+
+ return ret;
+}
+
+static int unicam_media_dev_init(struct unicam_device *unicam)
+{
+ int ret = 0;
+
+ unicam->mdev.dev = unicam->dev;
+ strscpy(unicam->mdev.model, UNICAM_MODULE_NAME,
+ sizeof(unicam->mdev.model));
+ strscpy(unicam->mdev.serial, "", sizeof(unicam->mdev.serial));
+ snprintf(unicam->mdev.bus_info, sizeof(unicam->mdev.bus_info),
+ "platform:%s", dev_name(unicam->dev));
+ unicam->mdev.hw_revision = 0;
+
+ media_device_init(&unicam->mdev);
+
+ unicam->v4l2_dev.mdev = &unicam->mdev;
+
+ ret = v4l2_device_register(unicam->dev, &unicam->v4l2_dev);
+ if (ret < 0) {
+ dev_err(unicam->dev,
+ "Unable to register v4l2 device\n");
+ return ret;
+ }
+
+ ret = media_device_register(&unicam->mdev);
+ if (ret < 0) {
+ dev_err(unicam->dev,
+ "Unable to register media-controller device\n");
+ goto err_v4l2_unregister;
+ }
+
+ return 0;
+
+err_v4l2_unregister:
+ v4l2_device_unregister(&unicam->v4l2_dev);
+ return ret;
+}
+
+static int unicam_probe(struct platform_device *pdev)
+{
+ struct unicam_device *unicam;
+ int ret = 0;
+
+ unicam = kzalloc(sizeof(*unicam), GFP_KERNEL);
+ if (!unicam)
+ return -ENOMEM;
+
+ kref_init(&unicam->kref);
+ unicam->dev = &pdev->dev;
+ /* set the driver data in platform device */
+ platform_set_drvdata(pdev, unicam);
+
+ unicam->base = devm_platform_ioremap_resource_byname(pdev, "unicam");
+ if (IS_ERR(unicam->base)) {
+ ret = PTR_ERR(unicam->base);
+ goto err_unicam_put;
+ }
+
+ unicam->clk_gate_base = devm_platform_ioremap_resource_byname(pdev, "cmi");
+ if (IS_ERR(unicam->clk_gate_base)) {
+ ret = PTR_ERR(unicam->clk_gate_base);
+ goto err_unicam_put;
+ }
+
+ unicam->clock = devm_clk_get(&pdev->dev, "lp");
+ if (IS_ERR(unicam->clock)) {
+ dev_err(unicam->dev, "Failed to get lp clock\n");
+ ret = PTR_ERR(unicam->clock);
+ goto err_unicam_put;
+ }
+
+ unicam->vpu_clock = devm_clk_get(&pdev->dev, "vpu");
+ if (IS_ERR(unicam->vpu_clock)) {
+ dev_err(unicam->dev, "Failed to get vpu clock\n");
+ ret = PTR_ERR(unicam->vpu_clock);
+ goto err_unicam_put;
+ }
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret <= 0) {
+ dev_err(&pdev->dev, "No IRQ resource\n");
+ ret = -EINVAL;
+ goto err_unicam_put;
+ }
+
+ ret = devm_request_irq(&pdev->dev, ret, unicam_isr, 0,
+ "unicam_capture0", unicam);
+ if (ret) {
+ dev_err(&pdev->dev, "Unable to request interrupt\n");
+ ret = -EINVAL;
+ goto err_unicam_put;
+ }
+
+ ret = unicam_media_dev_init(unicam);
+ if (ret) {
+ dev_err(unicam->dev,
+ "Unable to initialize media device\n");
+ goto err_unicam_put;
+ }
+
+ ret = unicam_init_and_register_subdev(unicam);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register internal subdev\n");
+ goto err_media_unregister;
+ }
+
+ ret = unicam_connect_of_subdevs(unicam);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to connect subdevs\n");
+ goto err_subdev_unregister;
+ }
+
+ /* Enable the block power domain */
+ pm_runtime_enable(&pdev->dev);
+
+ return 0;
+
+err_subdev_unregister:
+ v4l2_subdev_cleanup(&unicam->subdev.sd);
+err_media_unregister:
+ media_device_unregister(&unicam->mdev);
+err_unicam_put:
+ unicam_put(unicam);
+
+ return ret;
+}
+
+static int unicam_remove(struct platform_device *pdev)
+{
+ struct unicam_device *unicam = platform_get_drvdata(pdev);
+
+ unicam_unregister_nodes(unicam);
+ v4l2_device_unregister(&unicam->v4l2_dev);
+ media_device_unregister(&unicam->mdev);
+ v4l2_async_nf_unregister(&unicam->notifier);
+ unicam_put(unicam);
+
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id unicam_of_match[] = {
+ { .compatible = "brcm,bcm2835-unicam", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, unicam_of_match);
+
+static struct platform_driver unicam_driver = {
+ .probe = unicam_probe,
+ .remove = unicam_remove,
+ .driver = {
+ .name = UNICAM_MODULE_NAME,
+ .of_match_table = of_match_ptr(unicam_of_match),
+ },
+};
+
+module_platform_driver(unicam_driver);
+
+MODULE_AUTHOR("Dave Stevenson <[email protected]>");
+MODULE_DESCRIPTION("BCM2835 Unicam driver");
+MODULE_LICENSE("GPL");
--
2.32.0


2022-02-09 10:09:36

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v5 08/11] media: imx219: Switch from open to init_cfg

On Tue, Feb 08, 2022 at 06:02:50PM +0200, Laurent Pinchart wrote:
> Hi Jean-Michel,
>
> Thank you for the patch.
>
> On Tue, Feb 08, 2022 at 04:50:24PM +0100, Jean-Michel Hautbois wrote:
> > Use the init_cfg pad level operation instead of the internal subdev
> > open operation to set default formats on the pads.
> > While at it, make the imx219_pad_ops more easier to read.
>
> It could have been useful to explain here why there's a separate
> imx219_init_formats() function, as reviewers could wonder. It's clear
> when reviewing the next patches though.
>
> > Signed-off-by: Jean-Michel Hautbois <[email protected]>
> > ---
> > drivers/media/i2c/imx219.c | 63 +++++++++++++++-----------------------
> > 1 file changed, 25 insertions(+), 38 deletions(-)
> >
> > diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c
> > index 74dba5e61201..abcaee15c4a0 100644
> > --- a/drivers/media/i2c/imx219.c
> > +++ b/drivers/media/i2c/imx219.c
> > @@ -685,34 +685,6 @@ static void imx219_set_default_format(struct imx219 *imx219)
> > fmt->field = V4L2_FIELD_NONE;
> > }
> >
> > -static int imx219_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
> > -{
> > - struct imx219 *imx219 = to_imx219(sd);
> > - struct v4l2_mbus_framefmt *try_fmt =
> > - v4l2_subdev_get_try_format(sd, fh->state, 0);
> > - struct v4l2_rect *try_crop;
> > -
> > - mutex_lock(&imx219->mutex);
> > -
> > - /* Initialize try_fmt */
> > - try_fmt->width = supported_modes[0].width;
> > - try_fmt->height = supported_modes[0].height;
> > - try_fmt->code = imx219_get_format_code(imx219,
> > - MEDIA_BUS_FMT_SRGGB10_1X10);
> > - try_fmt->field = V4L2_FIELD_NONE;
> > -
> > - /* Initialize try_crop rectangle. */
> > - try_crop = v4l2_subdev_get_try_crop(sd, fh->state, 0);
> > - try_crop->top = IMX219_PIXEL_ARRAY_TOP;
> > - try_crop->left = IMX219_PIXEL_ARRAY_LEFT;
> > - try_crop->width = IMX219_PIXEL_ARRAY_WIDTH;
> > - try_crop->height = IMX219_PIXEL_ARRAY_HEIGHT;
>
> The crop rectangle initialization is removed here, but not added below.
>
> > -
> > - mutex_unlock(&imx219->mutex);
> > -
> > - return 0;
> > -}
> > -
> > static int imx219_set_ctrl(struct v4l2_ctrl *ctrl)
> > {
> > struct imx219 *imx219 =
> > @@ -802,6 +774,25 @@ static const struct v4l2_ctrl_ops imx219_ctrl_ops = {
> > .s_ctrl = imx219_set_ctrl,
> > };
> >
> > +static void imx219_init_formats(struct v4l2_subdev_state *state)
> > +{
> > + struct v4l2_mbus_framefmt *format;
> > +
> > + format = v4l2_state_get_stream_format(state, 0, 0);

Also, this won't work until you create routes. The function will return
NULL and you'll crash. This is fixed in subsequent patches, but it
breaks bisection.

> > + format->code = imx219_mbus_formats[0];
> > + format->width = supported_modes[0].width;
> > + format->height = supported_modes[0].height;
> > + format->field = V4L2_FIELD_NONE;
> > + format->colorspace = V4L2_COLORSPACE_RAW;
> > +}
> > +
> > +static int imx219_init_cfg(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state)
> > +{
> > + imx219_init_formats(state);
> > + return 0;
> > +}
> > +
> > static int imx219_enum_mbus_code(struct v4l2_subdev *sd,
> > struct v4l2_subdev_state *sd_state,
> > struct v4l2_subdev_mbus_code_enum *code)
> > @@ -1255,11 +1246,12 @@ static const struct v4l2_subdev_video_ops imx219_video_ops = {
> > };
> >
> > static const struct v4l2_subdev_pad_ops imx219_pad_ops = {
> > - .enum_mbus_code = imx219_enum_mbus_code,
> > - .get_fmt = imx219_get_pad_format,
> > - .set_fmt = imx219_set_pad_format,
> > - .get_selection = imx219_get_selection,
> > - .enum_frame_size = imx219_enum_frame_size,
> > + .init_cfg = imx219_init_cfg,
> > + .enum_mbus_code = imx219_enum_mbus_code,
> > + .get_fmt = imx219_get_pad_format,
> > + .set_fmt = imx219_set_pad_format,
> > + .get_selection = imx219_get_selection,
> > + .enum_frame_size = imx219_enum_frame_size,
> > };
> >
> > static const struct v4l2_subdev_ops imx219_subdev_ops = {
> > @@ -1268,10 +1260,6 @@ static const struct v4l2_subdev_ops imx219_subdev_ops = {
> > .pad = &imx219_pad_ops,
> > };
> >
> > -static const struct v4l2_subdev_internal_ops imx219_internal_ops = {
> > - .open = imx219_open,
> > -};
> > -
> > /* Initialize control handlers */
> > static int imx219_init_controls(struct imx219 *imx219)
> > {
> > @@ -1520,7 +1508,6 @@ static int imx219_probe(struct i2c_client *client)
> > goto error_power_off;
> >
> > /* Initialize subdev */
> > - imx219->sd.internal_ops = &imx219_internal_ops;
> > imx219->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
> > V4L2_SUBDEV_FL_HAS_EVENTS;
> > imx219->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
>
> --
> Regards,
>
> Laurent Pinchart

--
Regards,

Laurent Pinchart

2022-02-09 10:53:17

by Jean-Michel Hautbois

[permalink] [raw]
Subject: [PATCH v5 09/11] media: imx219: Introduce the set_routing operation

As we want to use multiplexed streams API, we need to be able to set the
pad routing. Introduce the set_routing operation.

As this operation is required for a multiplexed able sensor, add the
V4L2_SUBDEV_FL_MULTIPLEXED flag.

Signed-off-by: Jean-Michel Hautbois <[email protected]>
---
drivers/media/i2c/imx219.c | 82 ++++++++++++++++++++++++++++++++++++--
1 file changed, 79 insertions(+), 3 deletions(-)

diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c
index abcaee15c4a0..35b61fad8e35 100644
--- a/drivers/media/i2c/imx219.c
+++ b/drivers/media/i2c/imx219.c
@@ -118,6 +118,10 @@
#define IMX219_PIXEL_ARRAY_WIDTH 3280U
#define IMX219_PIXEL_ARRAY_HEIGHT 2464U

+/* Embedded metadata stream structure */
+#define IMX219_EMBEDDED_LINE_WIDTH 16384
+#define IMX219_NUM_EMBEDDED_LINES 1
+
struct imx219_reg {
u16 address;
u8 val;
@@ -784,15 +788,85 @@ static void imx219_init_formats(struct v4l2_subdev_state *state)
format->height = supported_modes[0].height;
format->field = V4L2_FIELD_NONE;
format->colorspace = V4L2_COLORSPACE_RAW;
+
+ if (state->routing.routes[1].flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE) {
+ format = v4l2_state_get_stream_format(state, 0, 1);
+ format->code = MEDIA_BUS_FMT_METADATA_8;
+ format->width = IMX219_EMBEDDED_LINE_WIDTH;
+ format->height = 1;
+ format->field = V4L2_FIELD_NONE;
+ format->colorspace = V4L2_COLORSPACE_DEFAULT;
+ }
}

-static int imx219_init_cfg(struct v4l2_subdev *sd,
- struct v4l2_subdev_state *state)
+static int __imx219_set_routing(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state)
{
+ struct v4l2_subdev_route routes[] = {
+ {
+ .source_pad = 0,
+ .source_stream = 0,
+ .flags = V4L2_SUBDEV_ROUTE_FL_IMMUTABLE |
+ V4L2_SUBDEV_ROUTE_FL_SOURCE |
+ V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+ },
+ {
+ .source_pad = 0,
+ .source_stream = 1,
+ .flags = V4L2_SUBDEV_ROUTE_FL_SOURCE |
+ V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+ }
+ };
+
+ struct v4l2_subdev_krouting routing = {
+ .num_routes = ARRAY_SIZE(routes),
+ .routes = routes,
+ };
+
+ int ret;
+
+ ret = v4l2_subdev_set_routing(sd, state, &routing);
+ if (ret)
+ return ret;
+
imx219_init_formats(state);
+
return 0;
}

+static int imx219_set_routing(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ enum v4l2_subdev_format_whence which,
+ struct v4l2_subdev_krouting *routing)
+{
+ int ret;
+
+ if (routing->num_routes == 0 || routing->num_routes > 2)
+ return -EINVAL;
+
+ v4l2_subdev_lock_state(state);
+
+ ret = __imx219_set_routing(sd, state);
+
+ v4l2_subdev_unlock_state(state);
+
+ return ret;
+}
+
+static int imx219_init_cfg(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state)
+{
+ int ret;
+
+ v4l2_subdev_lock_state(state);
+
+ ret = __imx219_set_routing(sd, state);
+
+ v4l2_subdev_unlock_state(state);
+
+ return ret;
+}
+
static int imx219_enum_mbus_code(struct v4l2_subdev *sd,
struct v4l2_subdev_state *sd_state,
struct v4l2_subdev_mbus_code_enum *code)
@@ -1251,6 +1325,7 @@ static const struct v4l2_subdev_pad_ops imx219_pad_ops = {
.get_fmt = imx219_get_pad_format,
.set_fmt = imx219_set_pad_format,
.get_selection = imx219_get_selection,
+ .set_routing = imx219_set_routing,
.enum_frame_size = imx219_enum_frame_size,
};

@@ -1509,7 +1584,8 @@ static int imx219_probe(struct i2c_client *client)

/* Initialize subdev */
imx219->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
- V4L2_SUBDEV_FL_HAS_EVENTS;
+ V4L2_SUBDEV_FL_HAS_EVENTS |
+ V4L2_SUBDEV_FL_MULTIPLEXED;
imx219->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;

/* Initialize source pad */
--
2.32.0


2022-02-09 11:02:18

by Jean-Michel Hautbois

[permalink] [raw]
Subject: [PATCH v5 01/11] media: v4l: Add V4L2-PIX-FMT-Y12P format

This is a packed grey-scale image format with a depth of 12 bits per
pixel. Two consecutive pixels are packed into 3 bytes. The first 2 bytes
contain the 8 high order bits of the pixels, and the 3rd byte contains
the 4 least significants bits of each pixel, in the same order.

Add the entry in userspace API, and document it.

Signed-off-by: Jean-Michel Hautbois <[email protected]>
Reviewed-by: Laurent Pinchart <[email protected]>
---
.../userspace-api/media/v4l/pixfmt-yuv-luma.rst | 11 +++++++++++
drivers/media/v4l2-core/v4l2-ioctl.c | 1 +
include/uapi/linux/videodev2.h | 1 +
3 files changed, 13 insertions(+)

diff --git a/Documentation/userspace-api/media/v4l/pixfmt-yuv-luma.rst b/Documentation/userspace-api/media/v4l/pixfmt-yuv-luma.rst
index 8ebd58c3588f..d37ce6027095 100644
--- a/Documentation/userspace-api/media/v4l/pixfmt-yuv-luma.rst
+++ b/Documentation/userspace-api/media/v4l/pixfmt-yuv-luma.rst
@@ -92,6 +92,17 @@ are often referred to as greyscale formats.
- ...
- ...

+ * .. _V4L2-PIX-FMT-Y12P:
+
+ - ``V4L2_PIX_FMT_Y12P``
+ - 'Y12P'
+
+ - Y'\ :sub:`0`\ [11:4]
+ - Y'\ :sub:`1`\ [11:4]
+ - Y'\ :sub:`1`\ [3:0] Y'\ :sub:`0`\ [3:0]
+ - ...
+ - ...
+
* .. _V4L2-PIX-FMT-Y14:

- ``V4L2_PIX_FMT_Y14``
diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
index 8a9883bbe1ef..12862e0803bf 100644
--- a/drivers/media/v4l2-core/v4l2-ioctl.c
+++ b/drivers/media/v4l2-core/v4l2-ioctl.c
@@ -1266,6 +1266,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
case V4L2_PIX_FMT_Y16_BE: descr = "16-bit Greyscale BE"; break;
case V4L2_PIX_FMT_Y10BPACK: descr = "10-bit Greyscale (Packed)"; break;
case V4L2_PIX_FMT_Y10P: descr = "10-bit Greyscale (MIPI Packed)"; break;
+ case V4L2_PIX_FMT_Y12P: descr = "12-bit Greyscale (MIPI Packed)"; break;
case V4L2_PIX_FMT_Y8I: descr = "Interleaved 8-bit Greyscale"; break;
case V4L2_PIX_FMT_Y12I: descr = "Interleaved 12-bit Greyscale"; break;
case V4L2_PIX_FMT_Z16: descr = "16-bit Depth"; break;
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index c9ee5de1bce6..bef488205fea 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -569,6 +569,7 @@ struct v4l2_pix_format {
/* Grey bit-packed formats */
#define V4L2_PIX_FMT_Y10BPACK v4l2_fourcc('Y', '1', '0', 'B') /* 10 Greyscale bit-packed */
#define V4L2_PIX_FMT_Y10P v4l2_fourcc('Y', '1', '0', 'P') /* 10 Greyscale, MIPI RAW10 packed */
+#define V4L2_PIX_FMT_Y12P v4l2_fourcc('Y', '1', '2', 'P') /* 12 Greyscale, MIPI RAW12 packed */

/* Palette formats */
#define V4L2_PIX_FMT_PAL8 v4l2_fourcc('P', 'A', 'L', '8') /* 8 8-bit palette */
--
2.32.0


2022-02-09 11:31:13

by Jean-Michel Hautbois

[permalink] [raw]
Subject: [PATCH v5 06/11] ARM: dts: bcm2711: Add unicam CSI nodes

Add both MIPI CSI-2 nodes in the core bcm2711 tree. Use the 3-cells
interrupt declaration, corresponding clocks and default as disabled.

Signed-off-by: Jean-Michel Hautbois <[email protected]>
Reviewed-by: Laurent Pinchart <[email protected]>
---
arch/arm/boot/dts/bcm2711-rpi.dtsi | 15 +++++++++++++++
arch/arm/boot/dts/bcm2711.dtsi | 22 ++++++++++++++++++++++
2 files changed, 37 insertions(+)

diff --git a/arch/arm/boot/dts/bcm2711-rpi.dtsi b/arch/arm/boot/dts/bcm2711-rpi.dtsi
index ca266c5d9f9b..97ee494891af 100644
--- a/arch/arm/boot/dts/bcm2711-rpi.dtsi
+++ b/arch/arm/boot/dts/bcm2711-rpi.dtsi
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include "bcm2835-rpi.dtsi"

+#include <dt-bindings/power/raspberrypi-power.h>
#include <dt-bindings/reset/raspberrypi,firmware-reset.h>

/ {
@@ -18,6 +19,20 @@ aliases {
};
};

+&csi0 {
+ clocks = <&clocks BCM2835_CLOCK_CAM0>,
+ <&firmware_clocks 4>;
+ clock-names = "lp", "vpu";
+ power-domains = <&power RPI_POWER_DOMAIN_UNICAM0>;
+};
+
+&csi1 {
+ clocks = <&clocks BCM2835_CLOCK_CAM1>,
+ <&firmware_clocks 4>;
+ clock-names = "lp", "vpu";
+ power-domains = <&power RPI_POWER_DOMAIN_UNICAM1>;
+};
+
&firmware {
firmware_clocks: clocks {
compatible = "raspberrypi,firmware-clocks";
diff --git a/arch/arm/boot/dts/bcm2711.dtsi b/arch/arm/boot/dts/bcm2711.dtsi
index dff18fc9a906..21eb10122e50 100644
--- a/arch/arm/boot/dts/bcm2711.dtsi
+++ b/arch/arm/boot/dts/bcm2711.dtsi
@@ -293,6 +293,28 @@ hvs: hvs@7e400000 {
interrupts = <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>;
};

+ csi0: csi@7e800000 {
+ compatible = "brcm,bcm2835-unicam";
+ reg = <0x7e800000 0x800>,
+ <0x7e802000 0x4>;
+ reg-names = "unicam", "cmi";
+ interrupts = <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ port {
+ };
+ };
+
+ csi1: csi@7e801000 {
+ compatible = "brcm,bcm2835-unicam";
+ reg = <0x7e801000 0x800>,
+ <0x7e802004 0x4>;
+ reg-names = "unicam", "cmi";
+ interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
+ status = "disabled";
+ port {
+ };
+ };
+
pixelvalve3: pixelvalve@7ec12000 {
compatible = "brcm,bcm2711-pixelvalve3";
reg = <0x7ec12000 0x100>;
--
2.32.0


2022-02-09 12:12:48

by Stefan Wahren

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Hi Jean-Michel,

Am 08.02.22 um 16:50 schrieb Jean-Michel Hautbois:
> Add driver for the Unicam camera receiver block on BCM283x processors.
> It is represented as two video device nodes: unicam-image and
> unicam-embedded which are connected to an internal subdev (named
> unicam-subdev) in order to manage streams routing.
>
> Signed-off-by: Dave Stevenson <[email protected]>
> Signed-off-by: Naushir Patuck <[email protected]>
> Signed-off-by: Jean-Michel Hautbois <[email protected]>
>
> ---
> v4:
> - Add the vendor prefox for DT name
> - Use the reg-names in DT parsing
> - Remove MAINTAINERS entry
>
> v3 main changes:
> - Change code organization
> - Remove unused variables
> - Correct the fmt_meta functions
> - Rewrite the start/stop streaming
> - You can now start the image node alone, but not the metadata one
> - The buffers are allocated per-node
> - only the required stream is started, if the route exists and is
> enabled
> - Prefix the macros with UNICAM_ to not have too generic names
> - Drop colorspace support
> -> This is causing issues in the try-fmt v4l2-compliance test
> test VIDIOC_G_FMT: OK
> fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> test VIDIOC_TRY_FMT: FAIL
> fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> test VIDIOC_S_FMT: FAIL
>
> v2: Remove the unicam_{info,debug,error} macros and use
> dev_dbg/dev_err instead.
> ---
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 2 +
> drivers/media/platform/bcm2835/Kconfig | 21 +
> drivers/media/platform/bcm2835/Makefile | 3 +
> .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> 6 files changed, 2850 insertions(+)
> create mode 100644 drivers/media/platform/bcm2835/Kconfig
> create mode 100644 drivers/media/platform/bcm2835/Makefile
> create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c
>
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 9fbdba0fd1e7..033b0358fbb8 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -170,6 +170,7 @@ source "drivers/media/platform/am437x/Kconfig"
> source "drivers/media/platform/xilinx/Kconfig"
> source "drivers/media/platform/rcar-vin/Kconfig"
> source "drivers/media/platform/atmel/Kconfig"
> +source "drivers/media/platform/bcm2835/Kconfig"
> source "drivers/media/platform/sunxi/Kconfig"
>
> config VIDEO_TI_CAL
> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> index 28eb4aadbf45..18894fc586aa 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -86,6 +86,8 @@ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom/camss/
>
> obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
>
> +obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835/
> +
> obj-y += sunxi/
>
> obj-$(CONFIG_VIDEO_MESON_GE2D) += meson/ge2d/
> diff --git a/drivers/media/platform/bcm2835/Kconfig b/drivers/media/platform/bcm2835/Kconfig
> new file mode 100644
> index 000000000000..1691541da905
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/Kconfig
> @@ -0,0 +1,21 @@
> +# Broadcom VideoCore4 V4L2 camera support
> +
> +config VIDEO_BCM2835_UNICAM
> + tristate "Broadcom BCM283x/BCM271x Unicam video capture driver"
> + depends on ARCH_BCM2835 || COMPILE_TEST
> + depends on VIDEO_V4L2
> + select MEDIA_CONTROLLER
> + select VIDEO_V4L2_SUBDEV_API
> + select V4L2_FWNODE
> + select VIDEOBUF2_DMA_CONTIG
> + help
> + Say Y here to enable support for the BCM283x/BCM271x CSI-2 receiver.
> + This is a V4L2 driver that controls the CSI-2 receiver directly,
> + independently from the VC4 firmware.
> + This driver is mutually exclusive with the use of bcm2835-camera. The
> + firmware will disable all access to the peripheral from within the
> + firmware if it finds a DT node using it, and bcm2835-camera will
> + therefore fail to probe.
> +
> + To compile this driver as a module, choose M here. The module will be
> + called bcm2835-unicam.
> diff --git a/drivers/media/platform/bcm2835/Makefile b/drivers/media/platform/bcm2835/Makefile
> new file mode 100644
> index 000000000000..a98aba03598a
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/Makefile
> @@ -0,0 +1,3 @@
> +# Makefile for BCM2835 Unicam driver
> +
> +obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835-unicam.o
> diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> new file mode 100644
> index 000000000000..b8d297076a02
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> @@ -0,0 +1,253 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +
> +/*
> + * Copyright (C) 2017-2020 Raspberry Pi Trading.
> + * Dave Stevenson <[email protected]>
> + */
> +
> +#ifndef VC4_REGS_UNICAM_H
> +#define VC4_REGS_UNICAM_H
> +
> +/*
> + * The following values are taken from files found within the code drop
> + * made by Broadcom for the BCM21553 Graphics Driver, predominantly in
> + * brcm_usrlib/dag/vmcsx/vcinclude/hardware_vc4.h.
> + * They have been modified to be only the register offset.
> + */
> +#define UNICAM_CTRL 0x000
> +#define UNICAM_STA 0x004
> +#define UNICAM_ANA 0x008
> +#define UNICAM_PRI 0x00c
> +#define UNICAM_CLK 0x010
> +#define UNICAM_CLT 0x014
> +#define UNICAM_DAT0 0x018
> +#define UNICAM_DAT1 0x01c
> +#define UNICAM_DAT2 0x020
> +#define UNICAM_DAT3 0x024
> +#define UNICAM_DLT 0x028
> +#define UNICAM_CMP0 0x02c
> +#define UNICAM_CMP1 0x030
> +#define UNICAM_CAP0 0x034
> +#define UNICAM_CAP1 0x038
> +#define UNICAM_ICTL 0x100
> +#define UNICAM_ISTA 0x104
> +#define UNICAM_IDI0 0x108
> +#define UNICAM_IPIPE 0x10c
> +#define UNICAM_IBSA0 0x110
> +#define UNICAM_IBEA0 0x114
> +#define UNICAM_IBLS 0x118
> +#define UNICAM_IBWP 0x11c
> +#define UNICAM_IHWIN 0x120
> +#define UNICAM_IHSTA 0x124
> +#define UNICAM_IVWIN 0x128
> +#define UNICAM_IVSTA 0x12c
> +#define UNICAM_ICC 0x130
> +#define UNICAM_ICS 0x134
> +#define UNICAM_IDC 0x138
> +#define UNICAM_IDPO 0x13c
> +#define UNICAM_IDCA 0x140
> +#define UNICAM_IDCD 0x144
> +#define UNICAM_IDS 0x148
> +#define UNICAM_DCS 0x200
> +#define UNICAM_DBSA0 0x204
> +#define UNICAM_DBEA0 0x208
> +#define UNICAM_DBWP 0x20c
> +#define UNICAM_DBCTL 0x300
> +#define UNICAM_IBSA1 0x304
> +#define UNICAM_IBEA1 0x308
> +#define UNICAM_IDI1 0x30c
> +#define UNICAM_DBSA1 0x310
> +#define UNICAM_DBEA1 0x314
> +#define UNICAM_MISC 0x400
> +
> +/*
> + * The following bitmasks are from the kernel released by Broadcom
> + * for Android - https://android.googlesource.com/kernel/bcm/
> + * The Rhea, Hawaii, and Java chips all contain the same VideoCore4
> + * Unicam block as BCM2835, as defined in eg
> + * arch/arm/mach-rhea/include/mach/rdb_A0/brcm_rdb_cam.h and similar.
> + * Values reworked to use the kernel BIT and GENMASK macros.
> + *
> + * Some of the bit mnenomics have been amended to match the datasheet.
> + */
> +/* UNICAM_CTRL Register */
> +#define UNICAM_CPE BIT(0)
> +#define UNICAM_MEM BIT(1)
> +#define UNICAM_CPR BIT(2)
> +#define UNICAM_CPM_MASK GENMASK(3, 3)
> +#define UNICAM_CPM_CSI2 0
> +#define UNICAM_CPM_CCP2 1
> +#define UNICAM_SOE BIT(4)
> +#define UNICAM_DCM_MASK GENMASK(5, 5)
> +#define UNICAM_DCM_STROBE 0
> +#define UNICAM_DCM_DATA 1
> +#define UNICAM_SLS BIT(6)
> +#define UNICAM_PFT_MASK GENMASK(11, 8)
> +#define UNICAM_OET_MASK GENMASK(20, 12)
> +
> +/* UNICAM_STA Register */
> +#define UNICAM_SYN BIT(0)
> +#define UNICAM_CS BIT(1)
> +#define UNICAM_SBE BIT(2)
> +#define UNICAM_PBE BIT(3)
> +#define UNICAM_HOE BIT(4)
> +#define UNICAM_PLE BIT(5)
> +#define UNICAM_SSC BIT(6)
> +#define UNICAM_CRCE BIT(7)
> +#define UNICAM_OES BIT(8)
> +#define UNICAM_IFO BIT(9)
> +#define UNICAM_OFO BIT(10)
> +#define UNICAM_BFO BIT(11)
> +#define UNICAM_DL BIT(12)
> +#define UNICAM_PS BIT(13)
> +#define UNICAM_IS BIT(14)
> +#define UNICAM_PI0 BIT(15)
> +#define UNICAM_PI1 BIT(16)
> +#define UNICAM_FSI_S BIT(17)
> +#define UNICAM_FEI_S BIT(18)
> +#define UNICAM_LCI_S BIT(19)
> +#define UNICAM_BUF0_RDY BIT(20)
> +#define UNICAM_BUF0_NO BIT(21)
> +#define UNICAM_BUF1_RDY BIT(22)
> +#define UNICAM_BUF1_NO BIT(23)
> +#define UNICAM_DI BIT(24)
> +
> +#define UNICAM_STA_MASK_ALL \
> + (UNICAM_DL | \
> + UNICAM_SBE | \
> + UNICAM_PBE | \
> + UNICAM_HOE | \
> + UNICAM_PLE | \
> + UNICAM_SSC | \
> + UNICAM_CRCE | \
> + UNICAM_IFO | \
> + UNICAM_OFO | \
> + UNICAM_PS | \
> + UNICAM_PI0 | \
> + UNICAM_PI1)
> +
> +/* UNICAM_ANA Register */
> +#define UNICAM_APD BIT(0)
> +#define UNICAM_BPD BIT(1)
> +#define UNICAM_AR BIT(2)
> +#define UNICAM_DDL BIT(3)
> +#define UNICAM_CTATADJ_MASK GENMASK(7, 4)
> +#define UNICAM_PTATADJ_MASK GENMASK(11, 8)
> +
> +/* UNICAM_PRI Register */
> +#define UNICAM_PE BIT(0)
> +#define UNICAM_PT_MASK GENMASK(2, 1)
> +#define UNICAM_NP_MASK GENMASK(7, 4)
> +#define UNICAM_PP_MASK GENMASK(11, 8)
> +#define UNICAM_BS_MASK GENMASK(15, 12)
> +#define UNICAM_BL_MASK GENMASK(17, 16)
> +
> +/* UNICAM_CLK Register */
> +#define UNICAM_CLE BIT(0)
> +#define UNICAM_CLPD BIT(1)
> +#define UNICAM_CLLPE BIT(2)
> +#define UNICAM_CLHSE BIT(3)
> +#define UNICAM_CLTRE BIT(4)
> +#define UNICAM_CLAC_MASK GENMASK(8, 5)
> +#define UNICAM_CLSTE BIT(29)
> +
> +/* UNICAM_CLT Register */
> +#define UNICAM_CLT1_MASK GENMASK(7, 0)
> +#define UNICAM_CLT2_MASK GENMASK(15, 8)
> +
> +/* UNICAM_DATn Registers */
> +#define UNICAM_DLE BIT(0)
> +#define UNICAM_DLPD BIT(1)
> +#define UNICAM_DLLPE BIT(2)
> +#define UNICAM_DLHSE BIT(3)
> +#define UNICAM_DLTRE BIT(4)
> +#define UNICAM_DLSM BIT(5)
> +#define UNICAM_DLFO BIT(28)
> +#define UNICAM_DLSTE BIT(29)
> +
> +#define UNICAM_DAT_MASK_ALL (UNICAM_DLSTE | UNICAM_DLFO)
> +
> +/* UNICAM_DLT Register */
> +#define UNICAM_DLT1_MASK GENMASK(7, 0)
> +#define UNICAM_DLT2_MASK GENMASK(15, 8)
> +#define UNICAM_DLT3_MASK GENMASK(23, 16)
> +
> +/* UNICAM_ICTL Register */
> +#define UNICAM_FSIE BIT(0)
> +#define UNICAM_FEIE BIT(1)
> +#define UNICAM_IBOB BIT(2)
> +#define UNICAM_FCM BIT(3)
> +#define UNICAM_TFC BIT(4)
> +#define UNICAM_LIP_MASK GENMASK(6, 5)
> +#define UNICAM_LCIE_MASK GENMASK(28, 16)
> +
> +/* UNICAM_IDI0/1 Register */
> +#define UNICAM_ID0_MASK GENMASK(7, 0)
> +#define UNICAM_ID1_MASK GENMASK(15, 8)
> +#define UNICAM_ID2_MASK GENMASK(23, 16)
> +#define UNICAM_ID3_MASK GENMASK(31, 24)
> +
> +/* UNICAM_ISTA Register */
> +#define UNICAM_FSI BIT(0)
> +#define UNICAM_FEI BIT(1)
> +#define UNICAM_LCI BIT(2)
> +
> +#define UNICAM_ISTA_MASK_ALL (UNICAM_FSI | UNICAM_FEI | UNICAM_LCI)
> +
> +/* UNICAM_IPIPE Register */
> +#define UNICAM_PUM_MASK GENMASK(2, 0)
> +/* Unpacking modes */
> +#define UNICAM_PUM_NONE 0
> +#define UNICAM_PUM_UNPACK6 1
> +#define UNICAM_PUM_UNPACK7 2
> +#define UNICAM_PUM_UNPACK8 3
> +#define UNICAM_PUM_UNPACK10 4
> +#define UNICAM_PUM_UNPACK12 5
> +#define UNICAM_PUM_UNPACK14 6
> +#define UNICAM_PUM_UNPACK16 7
> +#define UNICAM_DDM_MASK GENMASK(6, 3)
> +#define UNICAM_PPM_MASK GENMASK(9, 7)
> +/* Packing modes */
> +#define UNICAM_PPM_NONE 0
> +#define UNICAM_PPM_PACK8 1
> +#define UNICAM_PPM_PACK10 2
> +#define UNICAM_PPM_PACK12 3
> +#define UNICAM_PPM_PACK14 4
> +#define UNICAM_PPM_PACK16 5
> +#define UNICAM_DEM_MASK GENMASK(11, 10)
> +#define UNICAM_DEBL_MASK GENMASK(14, 12)
> +#define UNICAM_ICM_MASK GENMASK(16, 15)
> +#define UNICAM_IDM_MASK GENMASK(17, 17)
> +
> +/* UNICAM_ICC Register */
> +#define UNICAM_ICFL_MASK GENMASK(4, 0)
> +#define UNICAM_ICFH_MASK GENMASK(9, 5)
> +#define UNICAM_ICST_MASK GENMASK(12, 10)
> +#define UNICAM_ICLT_MASK GENMASK(15, 13)
> +#define UNICAM_ICLL_MASK GENMASK(31, 16)
> +
> +/* UNICAM_DCS Register */
> +#define UNICAM_DIE BIT(0)
> +#define UNICAM_DIM BIT(1)
> +#define UNICAM_DBOB BIT(3)
> +#define UNICAM_FDE BIT(4)
> +#define UNICAM_LDP BIT(5)
> +#define UNICAM_EDL_MASK GENMASK(15, 8)
> +
> +/* UNICAM_DBCTL Register */
> +#define UNICAM_DBEN BIT(0)
> +#define UNICAM_BUF0_IE BIT(1)
> +#define UNICAM_BUF1_IE BIT(2)
> +
> +/* UNICAM_CMP[0,1] register */
> +#define UNICAM_PCE BIT(31)
> +#define UNICAM_GI BIT(9)
> +#define UNICAM_CPH BIT(8)
> +#define UNICAM_PCVC_MASK GENMASK(7, 6)
> +#define UNICAM_PCDT_MASK GENMASK(5, 0)
> +
> +/* UNICAM_MISC register */
> +#define UNICAM_FL0 BIT(6)
> +#define UNICAM_FL1 BIT(9)
> +
> +#endif
> diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam.c b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> new file mode 100644
> index 000000000000..470e691637c7
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> @@ -0,0 +1,2570 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * BCM283x / BCM271x Unicam Capture Driver
> + *
> + * Copyright (C) 2017-2020 - Raspberry Pi (Trading) Ltd.
> + *
> + * Dave Stevenson <[email protected]>
> + *
> + * Based on TI am437x driver by
> + * Benoit Parrot <[email protected]>
> + * Lad, Prabhakar <[email protected]>
> + *
> + * and TI CAL camera interface driver by
> + * Benoit Parrot <[email protected]>
> + *
> + *
> + * There are two camera drivers in the kernel for BCM283x - this one
> + * and bcm2835-camera (currently in staging).
> + *
> + * This driver directly controls the Unicam peripheral - there is no
> + * involvement with the VideoCore firmware. Unicam receives CSI-2 or
> + * CCP2 data and writes it into SDRAM.
> + * The only potential processing options are to repack Bayer data into an
> + * alternate format, and applying windowing.
> + * The repacking does not shift the data, so can repack V4L2_PIX_FMT_Sxxxx10P
> + * to V4L2_PIX_FMT_Sxxxx10, or V4L2_PIX_FMT_Sxxxx12P to V4L2_PIX_FMT_Sxxxx12,
> + * but not generically up to V4L2_PIX_FMT_Sxxxx16. The driver will add both
> + * formats where the relevant formats are defined, and will automatically
> + * configure the repacking as required.
> + * Support for windowing may be added later.
> + *
> + * It should be possible to connect this driver to any sensor with a
> + * suitable output interface and V4L2 subdevice driver.
> + *
> + * bcm2835-camera uses the VideoCore firmware to control the sensor,
> + * Unicam, ISP, and all tuner control loops. Fully processed frames are
> + * delivered to the driver by the firmware. It only has sensor drivers
> + * for Omnivision OV5647, and Sony IMX219 sensors.
> + *
> + * The two drivers are mutually exclusive for the same Unicam instance.
> + * The VideoCore firmware checks the device tree configuration during boot.
> + * If it finds device tree nodes called csi0 or csi1 it will block the
> + * firmware from accessing the peripheral, and bcm2835-camera will
> + * not be able to stream data.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/of_graph.h>
> +#include <linux/pinctrl/consumer.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +#include <linux/videodev2.h>
> +
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-dv-timings.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "bcm2835-unicam-regs.h"
> +
> +#define UNICAM_MODULE_NAME "unicam"
> +
> +/*
> + * Unicam must request a minimum of 250Mhz from the VPU clock.
> + * Otherwise the input FIFOs overrun and cause image corruption.
> + */
> +#define UNICAM_MIN_VPU_CLOCK_RATE (250 * 1000 * 1000)
> +
> +/*
> + * Stride is a 16 bit register, but also has to be a multiple of 32.
> + */
> +#define UNICAM_BPL_ALIGNMENT 32
> +#define UNICAM_MAX_BYTESPERLINE ((1 << 16) - UNICAM_BPL_ALIGNMENT)
> +/*
> + * Max width is therefore determined by the max stride divided by
> + * the number of bits per pixel. Take 32bpp as a
> + * worst case.
> + * No imposed limit on the height, so adopt a square image for want
> + * of anything better.
> + */
> +#define UNICAM_MAX_WIDTH (UNICAM_MAX_BYTESPERLINE / 4)
> +#define UNICAM_MAX_HEIGHT UNICAM_MAX_WIDTH
> +/* Define a nominal minimum image size */
> +#define UNICAM_MIN_WIDTH 16
> +#define UNICAM_MIN_HEIGHT 16
> +/* Default size of the embedded buffer */
> +#define UNICAM_EMBEDDED_SIZE 16384
> +
> +/*
> + * Size of the dummy buffer. Can be any size really, but the DMA
> + * allocation works in units of page sizes.
> + */
> +#define UNICAM_DUMMY_BUF_SIZE PAGE_SIZE
> +
> +#define UNICAM_SD_PAD_SINK 0
> +#define UNICAM_SD_PAD_SOURCE_IMAGE 1
> +#define UNICAM_SD_PAD_SOURCE_METADATA 2
> +#define UNICAM_SD_NUM_PADS (1 + UNICAM_SD_PAD_SOURCE_METADATA)
> +
> +static inline bool unicam_sd_pad_is_sink(u32 pad)
> +{
> + /* Camera RX has 1 sink pad, and N source pads */
> + return pad == 0;
> +}
> +
> +static inline bool unicam_sd_pad_is_source(u32 pad)
> +{
> + /* Camera RX has 1 sink pad, and N source pads */
> + return pad != UNICAM_SD_PAD_SINK;
> +}
> +
> +enum unicam_node_type {
> + UNICAM_IMAGE_NODE,
> + UNICAM_METADATA_NODE,
> + UNICAM_MAX_NODES
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Structure definitions
> + */
> +
> +/*
> + * struct unicam_fmt - Unicam media bus format information
> + * @fourcc: V4L2 pixel format FCC identifier. 0 if n/a.
> + * @unpacked_fourcc: V4L2 pixel format FCC identifier if the data is expanded
> + * out to 16bpp. 0 if n/a.
> + * @code: V4L2 media bus format code.
> + * @depth: Bits per pixel as delivered from the source.
> + * @csi_dt: CSI data type.
> + * @metadata_fmt: This format only applies to the metadata pad.
> + */
> +struct unicam_fmt {
> + u32 fourcc;
> + u32 unpacked_fourcc;
> + u32 code;
> + u8 depth;
> + u8 csi_dt;
> + u8 metadata_fmt:1;
> +};
> +
> +static const struct unicam_fmt unicam_image_formats[] = {
> + /* YUV Formats */
> + {
> + .fourcc = V4L2_PIX_FMT_YUYV,
> + .code = MEDIA_BUS_FMT_YUYV8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + .fourcc = V4L2_PIX_FMT_UYVY,
> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + .fourcc = V4L2_PIX_FMT_YVYU,
> + .code = MEDIA_BUS_FMT_YVYU8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + .fourcc = V4L2_PIX_FMT_VYUY,
> + .code = MEDIA_BUS_FMT_VYUY8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + /* RGB Formats */
> + .fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */
> + .code = MEDIA_BUS_FMT_RGB565_1X16,
> + .depth = 16,
> + .csi_dt = 0x22,
> + }, {
> + .fourcc = V4L2_PIX_FMT_RGB24, /* rgb */
> + .code = MEDIA_BUS_FMT_RGB888_1X24,
> + .depth = 24,
> + .csi_dt = 0x24,
> + }, {
> + .fourcc = V4L2_PIX_FMT_BGR24, /* bgr */
> + .code = MEDIA_BUS_FMT_BGR888_1X24,
> + .depth = 24,
> + .csi_dt = 0x24,
> + }, {
> + /* Bayer Formats */
> + .fourcc = V4L2_PIX_FMT_SBGGR8,
> + .code = MEDIA_BUS_FMT_SBGGR8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG8,
> + .code = MEDIA_BUS_FMT_SGBRG8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG8,
> + .code = MEDIA_BUS_FMT_SGRBG8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB8,
> + .code = MEDIA_BUS_FMT_SRGGB8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SBGGR10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SBGGR10,
> + .code = MEDIA_BUS_FMT_SBGGR10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGBRG10,
> + .code = MEDIA_BUS_FMT_SGBRG10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGRBG10,
> + .code = MEDIA_BUS_FMT_SGRBG10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SRGGB10,
> + .code = MEDIA_BUS_FMT_SRGGB10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SBGGR12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SBGGR12,
> + .code = MEDIA_BUS_FMT_SBGGR12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGBRG12,
> + .code = MEDIA_BUS_FMT_SGBRG12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGRBG12,
> + .code = MEDIA_BUS_FMT_SGRBG12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SRGGB12,
> + .code = MEDIA_BUS_FMT_SRGGB12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SBGGR14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SBGGR14,
> + .code = MEDIA_BUS_FMT_SBGGR14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGBRG14,
> + .code = MEDIA_BUS_FMT_SGBRG14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGRBG14,
> + .code = MEDIA_BUS_FMT_SGRBG14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SRGGB14,
> + .code = MEDIA_BUS_FMT_SRGGB14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + /* 16 bit Bayer formats could be supported. */
> +
> + /* Greyscale formats */
> + .fourcc = V4L2_PIX_FMT_GREY,
> + .code = MEDIA_BUS_FMT_Y8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_Y10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_Y10,
> + .code = MEDIA_BUS_FMT_Y10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_Y12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_Y12,
> + .code = MEDIA_BUS_FMT_Y12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_Y14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_Y14,
> + .code = MEDIA_BUS_FMT_Y14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + },
> +};
> +
> +static const struct unicam_fmt unicam_meta_formats[] = {
> + {
> + .fourcc = V4L2_META_FMT_8,
> + .code = MEDIA_BUS_FMT_METADATA_8,
> + .depth = 8,
> + .metadata_fmt = 1,
> + }
> +};
> +
> +static const struct v4l2_mbus_framefmt default_image_format = {
> + .width = 640,
> + .height = 480,
> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_SRGB,
> + .ycbcr_enc = V4L2_YCBCR_ENC_601,
> + .quantization = V4L2_QUANTIZATION_LIM_RANGE,
> + .xfer_func = V4L2_XFER_FUNC_SRGB,
> + .flags = 0,
> +};
> +
> +struct unicam_buffer {
> + struct vb2_v4l2_buffer vb;
> + struct list_head list;
> +};
> +
> +static inline struct unicam_buffer *to_unicam_buffer(struct vb2_buffer *vb)
> +{
> + return container_of(vb, struct unicam_buffer, vb.vb2_buf);
> +}
> +
> +struct unicam_node {
> + bool registered;
> + bool streaming;
> + unsigned int id;
> +
> + /* Pointer to the current v4l2_buffer */
> + struct unicam_buffer *cur_frm;
> + /* Pointer to the next v4l2_buffer */
> + struct unicam_buffer *next_frm;
> + /* video capture */
> + const struct unicam_fmt *fmt;
> + /* Used to store current pixel format */
> + struct v4l2_format v_fmt;
> + /* Buffer queue used in video-buf */
> + struct vb2_queue buffer_queue;
> + /* Queue of filled frames */
> + struct list_head dma_queue;
> + /* IRQ lock for DMA queue */
> + spinlock_t dma_queue_lock;
> + /* lock used to access this structure */
> + struct mutex lock;
> + /* Identifies video device for this channel */
> + struct video_device video_dev;
> + /* Pointer to the parent handle */
> + struct unicam_device *dev;
> + struct media_pad pad;
> + /*
> + * Dummy buffer intended to be used by unicam
> + * if we have no other queued buffers to swap to.
> + */
> + void *dummy_buf_cpu_addr;
> + dma_addr_t dummy_buf_dma_addr;
> +};
> +
> +struct unicam_device {
> + struct kref kref;
> +
> + /* peripheral base address */
> + void __iomem *base;
> + /* clock gating base address */
> + void __iomem *clk_gate_base;
> + /* lp clock handle */
> + struct clk *clock;
> + /* vpu clock handle */
> + struct clk *vpu_clock;
> + /* V4l2 device */
> + struct v4l2_device v4l2_dev;
> + struct media_device mdev;
> +
> + /* parent device */
> + struct device *dev;
> + /* subdevice async Notifier */
> + struct v4l2_async_notifier notifier;
> + unsigned int sequence;
> +
> + /* Sensor node */
> + struct {
> + struct v4l2_subdev *subdev;
> + struct media_pad *pad;
> + } sensor;
> +
> + /* Internal subdev */
> + struct {
> + struct v4l2_subdev sd;
> + struct media_pad pads[UNICAM_SD_NUM_PADS];
> + bool streaming;
> + } subdev;
> +
> + enum v4l2_mbus_type bus_type;
> + /*
> + * Stores bus.mipi_csi2.flags for CSI2 sensors, or
> + * bus.mipi_csi1.strobe for CCP2.
> + */
> + unsigned int bus_flags;
> + unsigned int max_data_lanes;
> + unsigned int active_data_lanes;
> +
> + struct media_pipeline pipe;
> +
> + struct unicam_node node[UNICAM_MAX_NODES];
> +};
> +
> +static inline struct unicam_device *
> +notifier_to_unicam_device(struct v4l2_async_notifier *notifier)
> +{
> + return container_of(notifier, struct unicam_device, notifier);
> +}
> +
> +static inline struct unicam_device *
> +sd_to_unicam_device(struct v4l2_subdev *sd)
> +{
> + return container_of(sd, struct unicam_device, subdev.sd);
> +}
> +
> +static void unicam_release(struct kref *kref)
> +{
> + struct unicam_device *unicam =
> + container_of(kref, struct unicam_device, kref);
> +
> + if (unicam->mdev.dev)
> + media_device_cleanup(&unicam->mdev);
> +
> + kfree(unicam);
> +}
> +
> +static void unicam_put(struct unicam_device *unicam)
> +{
> + kref_put(&unicam->kref, unicam_release);
> +}
> +
> +static struct unicam_device *unicam_get(struct unicam_device *unicam)
> +{
> + kref_get(&unicam->kref);
> + return unicam;
> +}
> +
> +static void unicam_node_release(struct video_device *vdev)
> +{
> + struct unicam_node *node = video_get_drvdata(vdev);
> +
> + unicam_put(node->dev);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Misc helper functions
> + */
> +
> +static inline bool is_metadata_node(struct unicam_node *node)
> +{
> + return node->video_dev.device_caps & V4L2_CAP_META_CAPTURE;
> +}
> +
> +static inline bool is_image_node(struct unicam_node *node)
> +{
> + return node->video_dev.device_caps & V4L2_CAP_VIDEO_CAPTURE;
> +}
> +
> +/* Hardware access */
> +static inline void unicam_clk_write(struct unicam_device *unicam, u32 val)
> +{
> + /* Pass the CM_PASSWORD along with the value. */
> + writel(val | 0x5a000000, unicam->clk_gate_base);
> +}
> +
> +static inline u32 unicam_reg_read(struct unicam_device *unicam, u32 offset)
> +{
> + return readl(unicam->base + offset);
> +}
> +
> +static inline void unicam_reg_write(struct unicam_device *unicam, u32 offset, u32 val)
> +{
> + writel(val, unicam->base + offset);
> +}
> +
> +static inline int unicam_get_field(u32 value, u32 mask)
> +{
> + return (value & mask) >> __ffs(mask);
> +}
> +
> +static inline void unicam_set_field(u32 *valp, u32 field, u32 mask)
> +{
> + u32 val = *valp;
> +
> + val &= ~mask;
> + val |= (field << __ffs(mask)) & mask;
> + *valp = val;
> +}
> +
> +static inline void unicam_reg_write_field(struct unicam_device *unicam, u32 offset,
> + u32 field, u32 mask)
> +{
> + u32 val = unicam_reg_read(unicam, offset);
> +
> + unicam_set_field(&val, field, mask);
> + unicam_reg_write(unicam, offset, val);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Format data table and helper functions
> + */
> +
> +/* Format setup functions */
> +static const struct unicam_fmt *unicam_find_format_by_code(u32 code, u32 pad)
> +{
> + const struct unicam_fmt *formats;
> + unsigned int num_formats;
> + unsigned int i;
> +
> + if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + formats = unicam_image_formats;
> + num_formats = ARRAY_SIZE(unicam_image_formats);
> + } else {
> + formats = unicam_meta_formats;
> + num_formats = ARRAY_SIZE(unicam_meta_formats);
> + }
> +
> + for (i = 0; i < num_formats; i++) {
> + if (formats[i].code == code)
> + return &formats[i];
> + }
> +
> + return NULL;
> +}
> +
> +static const struct unicam_fmt *unicam_find_format_by_fourcc(u32 fourcc, u32 pad)
> +{
> + const struct unicam_fmt *formats;
> + unsigned int num_formats;
> + unsigned int i;
> +
> + if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + formats = unicam_image_formats;
> + num_formats = ARRAY_SIZE(unicam_image_formats);
> + } else {
> + formats = unicam_meta_formats;
> + num_formats = ARRAY_SIZE(unicam_meta_formats);
> + }
> +
> + for (i = 0; i < num_formats; ++i) {
> + if (formats[i].fourcc == fourcc)
> + return &formats[i];
> + }
> +
> + return NULL;
> +}
> +
> +static int unicam_calc_format_size_bpl(struct unicam_device *unicam,
> + const struct unicam_fmt *fmt,
> + struct v4l2_format *f)
> +{
> + u32 min_bpl;
> +
> + v4l_bound_align_image(&f->fmt.pix.width, UNICAM_MIN_WIDTH,
> + UNICAM_MAX_WIDTH, 2,
> + &f->fmt.pix.height, UNICAM_MIN_HEIGHT,
> + UNICAM_MAX_HEIGHT, 0, 0);
> +
> + /* Unpacking always goes to 16bpp */
> + if (f->fmt.pix.pixelformat == fmt->unpacked_fourcc)
> + min_bpl = f->fmt.pix.width * 2;
> + else
> + min_bpl = f->fmt.pix.width * fmt->depth / 8;
> + min_bpl = ALIGN(min_bpl, UNICAM_BPL_ALIGNMENT);
> +
> + f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline,
> + UNICAM_BPL_ALIGNMENT);
> + f->fmt.pix.bytesperline = clamp_t(unsigned int,
> + f->fmt.pix.bytesperline,
> + min_bpl,
> + UNICAM_MAX_BYTESPERLINE);
> +
> + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
> +
> + dev_dbg(unicam->dev, "%s: fourcc: %08X size: %ux%u bpl:%u img_size:%u\n",
> + __func__,
> + f->fmt.pix.pixelformat,
> + f->fmt.pix.width, f->fmt.pix.height,
> + f->fmt.pix.bytesperline, f->fmt.pix.sizeimage);
> +
> + return 0;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Hardware handling
> + */
> +
> +static void unicam_wr_dma_addr(struct unicam_node *node, dma_addr_t dmaaddr,
> + unsigned int buffer_size)
> +{
> + dma_addr_t endaddr = dmaaddr + buffer_size;
> +
> + if (node->id == UNICAM_IMAGE_NODE) {
> + unicam_reg_write(node->dev, UNICAM_IBSA0, dmaaddr);
> + unicam_reg_write(node->dev, UNICAM_IBEA0, endaddr);
> + } else {
> + unicam_reg_write(node->dev, UNICAM_DBSA0, dmaaddr);
> + unicam_reg_write(node->dev, UNICAM_DBEA0, endaddr);
> + }
> +}
> +
> +static unsigned int unicam_get_lines_done(struct unicam_device *unicam)
> +{
> + dma_addr_t start_addr, cur_addr;
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> + unsigned int stride = node->v_fmt.fmt.pix.bytesperline;
> + struct unicam_buffer *frm = node->cur_frm;
> +
> + if (!frm)
> + return 0;
> +
> + start_addr = vb2_dma_contig_plane_dma_addr(&frm->vb.vb2_buf, 0);
> + cur_addr = unicam_reg_read(unicam, UNICAM_IBWP);
> + return (unsigned int)(cur_addr - start_addr) / stride;
> +}
> +
> +static void unicam_schedule_next_buffer(struct unicam_node *node)
> +{
> + struct unicam_buffer *buf;
> + unsigned int size;
> + dma_addr_t addr;
> +
> + buf = list_first_entry(&node->dma_queue, struct unicam_buffer, list);
> + node->next_frm = buf;
> + list_del(&buf->list);
> +
> + addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> + if (is_image_node(node))
> + size = node->v_fmt.fmt.pix.sizeimage;
> + else
> + size = node->v_fmt.fmt.meta.buffersize;
> +
> + unicam_wr_dma_addr(node, addr, size);
> +}
> +
> +static void unicam_schedule_dummy_buffer(struct unicam_node *node)
> +{
> + struct unicam_device *unicam = node->dev;
> + int node_id = is_image_node(node) ? UNICAM_IMAGE_NODE : UNICAM_METADATA_NODE;
> +
> + dev_dbg(unicam->dev, "Scheduling dummy buffer for node %d\n", node_id);
> +
> + unicam_wr_dma_addr(node, node->dummy_buf_dma_addr,
> + UNICAM_DUMMY_BUF_SIZE);
> +
> + node->next_frm = NULL;
> +}
> +
> +static void unicam_process_buffer_complete(struct unicam_node *node,
> + unsigned int sequence)
> +{
> + node->cur_frm->vb.field = node->v_fmt.fmt.pix.field;
> + node->cur_frm->vb.sequence = sequence;
> +
> + vb2_buffer_done(&node->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +}
> +
> +static void unicam_queue_event_sof(struct unicam_device *unicam)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> +
> + struct v4l2_event event = {
> + .type = V4L2_EVENT_FRAME_SYNC,
> + .u.frame_sync.frame_sequence = unicam->sequence,
> + };
> +
> + v4l2_event_queue(&node->video_dev, &event);
> +}
> +
> +/*
> + * unicam_isr : ISR handler for unicam capture
> + * @irq: irq number
> + * @dev_id: dev_id ptr
> + *
> + * It changes status of the captured buffer, takes next buffer from the queue
> + * and sets its address in unicam registers
> + */
> +static irqreturn_t unicam_isr(int irq, void *dev)
> +{
> + struct unicam_device *unicam = dev;
> + unsigned int lines_done = unicam_get_lines_done(dev);
> + unsigned int sequence = unicam->sequence;
> + unsigned int i;
> + u32 ista, sta;
> + bool fe;
> + u64 ts;
> +
> + sta = unicam_reg_read(unicam, UNICAM_STA);
> + /* Write value back to clear the interrupts */
> + unicam_reg_write(unicam, UNICAM_STA, sta);
> +
> + ista = unicam_reg_read(unicam, UNICAM_ISTA);
> + /* Write value back to clear the interrupts */
> + unicam_reg_write(unicam, UNICAM_ISTA, ista);
> +
> + dev_dbg(unicam->dev, "ISR: ISTA: 0x%X, STA: 0x%X, sequence %d, lines done %d",
> + ista, sta, sequence, lines_done);
> +
> + if (!(sta & (UNICAM_IS | UNICAM_PI0)))
> + return IRQ_HANDLED;
> +
> + /*
> + * Look for either the Frame End interrupt or the Packet Capture status
> + * to signal a frame end.
> + */
> + fe = ista & UNICAM_FEI || sta & UNICAM_PI0;
> +
> + /*
> + * We must run the frame end handler first. If we have a valid next_frm
> + * and we get a simultaneout FE + FS interrupt, running the FS handler
> + * first would null out the next_frm ptr and we would have lost the
> + * buffer forever.
> + */
> + if (fe) {
> + /*
> + * Ensure we have swapped buffers already as we can't
> + * stop the peripheral. If no buffer is available, use a
> + * dummy buffer to dump out frames until we get a new buffer
> + * to use.
> + */
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + if (!unicam->node[i].streaming)
> + continue;
> +
> + /*
> + * If cur_frm == next_frm, it means we have not had
> + * a chance to swap buffers, likely due to having
> + * multiple interrupts occurring simultaneously (like FE
> + * + FS + LS). In this case, we cannot signal the buffer
> + * as complete, as the HW will reuse that buffer.
> + */
> + if (unicam->node[i].cur_frm &&
> + unicam->node[i].cur_frm != unicam->node[i].next_frm)
> + unicam_process_buffer_complete(&unicam->node[i],
> + sequence);
> + unicam->node[i].cur_frm = unicam->node[i].next_frm;
> + }
> + unicam->sequence++;
> + }
> +
> + if (ista & UNICAM_FSI) {
> + /*
> + * Timestamp is to be when the first data byte was captured,
> + * aka frame start.
> + */
> + ts = ktime_get_ns();
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + if (!unicam->node[i].streaming)
> + continue;
> +
> + if (unicam->node[i].cur_frm)
> + unicam->node[i].cur_frm->vb.vb2_buf.timestamp =
> + ts;
> + else
> + dev_dbg(unicam->v4l2_dev.dev,
> + "ISR: [%d] Dropping frame, buffer not available at FS\n",
> + i);
> + /*
> + * Set the next frame output to go to a dummy frame
> + * if we have not managed to obtain another frame
> + * from the queue.
> + */
> + unicam_schedule_dummy_buffer(&unicam->node[i]);
> + }
> +
> + unicam_queue_event_sof(unicam);
> + }
> +
> + /*
> + * Cannot swap buffer at frame end, there may be a race condition
> + * where the HW does not actually swap it if the new frame has
> + * already started.
> + */
> + if (ista & (UNICAM_FSI | UNICAM_LCI) && !fe) {
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + if (!unicam->node[i].streaming)
> + continue;
> +
> + spin_lock(&unicam->node[i].dma_queue_lock);
> + if (!list_empty(&unicam->node[i].dma_queue) &&
> + !unicam->node[i].next_frm)
> + unicam_schedule_next_buffer(&unicam->node[i]);
> + spin_unlock(&unicam->node[i].dma_queue_lock);
> + }
> + }
> +
> + if (unicam_reg_read(unicam, UNICAM_ICTL) & UNICAM_FCM) {
> + /* Switch out of trigger mode if selected */
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_TFC);
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 0, UNICAM_FCM);
> + }
> + return IRQ_HANDLED;
> +}
> +
> +static void unicam_set_packing_config(struct unicam_device *unicam)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> + u32 pack, unpack;
> + u32 val;
> +
> + if (node->v_fmt.fmt.pix.pixelformat ==
> + node->fmt->fourcc) {
> + unpack = UNICAM_PUM_NONE;
> + pack = UNICAM_PPM_NONE;
> + } else {
> + switch (node->fmt->depth) {
> + case 8:
> + unpack = UNICAM_PUM_UNPACK8;
> + break;
> + case 10:
> + unpack = UNICAM_PUM_UNPACK10;
> + break;
> + case 12:
> + unpack = UNICAM_PUM_UNPACK12;
> + break;
> + case 14:
> + unpack = UNICAM_PUM_UNPACK14;
> + break;
> + case 16:
> + unpack = UNICAM_PUM_UNPACK16;
> + break;
> + default:
> + unpack = UNICAM_PUM_NONE;
> + break;
> + }
> +
> + /* Repacking is always to 16bpp */
> + pack = UNICAM_PPM_PACK16;
> + }
> +
> + val = 0;
> + unicam_set_field(&val, unpack, UNICAM_PUM_MASK);
> + unicam_set_field(&val, pack, UNICAM_PPM_MASK);
> + unicam_reg_write(unicam, UNICAM_IPIPE, val);
> +}
> +
> +static void unicam_cfg_image_id(struct unicam_device *unicam)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> +
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + /* CSI2 mode, hardcode VC 0 for now. */
> + unicam_reg_write(unicam, UNICAM_IDI0,
> + (0 << 6) | node->fmt->csi_dt);
> + } else {
> + /* CCP2 mode */
> + unicam_reg_write(unicam, UNICAM_IDI0,
> + 0x80 | node->fmt->csi_dt);
> + }
> +}
> +
> +static void unicam_enable_ed(struct unicam_device *unicam)
> +{
> + u32 val = unicam_reg_read(unicam, UNICAM_DCS);
> +
> + unicam_set_field(&val, 2, UNICAM_EDL_MASK);
> + /* Do not wrap at the end of the embedded data buffer */
> + unicam_set_field(&val, 0, UNICAM_DBOB);
> +
> + unicam_reg_write(unicam, UNICAM_DCS, val);
> +}
> +
> +static void unicam_start_rx(struct unicam_device *unicam, dma_addr_t *addr)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> + int line_int_freq = node->v_fmt.fmt.pix.height >> 2;
> + unsigned int size, i;
> + u32 val;
> +
> + if (line_int_freq < 128)
> + line_int_freq = 128;
> +
> + /* Enable lane clocks */
> + val = 1;
> + for (i = 0; i < unicam->active_data_lanes; i++)
> + val = val << 2 | 1;
> + unicam_clk_write(unicam, val);
> +
> + /* Basic init */
> + unicam_reg_write(unicam, UNICAM_CTRL, UNICAM_MEM);
> +
> + /* Enable analogue control, and leave in reset. */
> + val = UNICAM_AR;
> + unicam_set_field(&val, 7, UNICAM_CTATADJ_MASK);
> + unicam_set_field(&val, 7, UNICAM_PTATADJ_MASK);
> + unicam_reg_write(unicam, UNICAM_ANA, val);
> + usleep_range(1000, 2000);
> +
> + /* Come out of reset */
> + unicam_reg_write_field(unicam, UNICAM_ANA, 0, UNICAM_AR);
> +
> + /* Peripheral reset */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPR);
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPR);
> +
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPE);
> +
> + /* Enable Rx control. */
> + val = unicam_reg_read(unicam, UNICAM_CTRL);
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + unicam_set_field(&val, UNICAM_CPM_CSI2, UNICAM_CPM_MASK);
> + unicam_set_field(&val, UNICAM_DCM_STROBE, UNICAM_DCM_MASK);
> + } else {
> + unicam_set_field(&val, UNICAM_CPM_CCP2, UNICAM_CPM_MASK);
> + unicam_set_field(&val, unicam->bus_flags, UNICAM_DCM_MASK);
> + }
> + /* Packet framer timeout */
> + unicam_set_field(&val, 0xf, UNICAM_PFT_MASK);
> + unicam_set_field(&val, 128, UNICAM_OET_MASK);
> + unicam_reg_write(unicam, UNICAM_CTRL, val);
> +
> + unicam_reg_write(unicam, UNICAM_IHWIN, 0);
> + unicam_reg_write(unicam, UNICAM_IVWIN, 0);
> +
> + /* AXI bus access QoS setup */
> + val = unicam_reg_read(unicam, UNICAM_PRI);
> + unicam_set_field(&val, 0, UNICAM_BL_MASK);
> + unicam_set_field(&val, 0, UNICAM_BS_MASK);
> + unicam_set_field(&val, 0xe, UNICAM_PP_MASK);
> + unicam_set_field(&val, 8, UNICAM_NP_MASK);
> + unicam_set_field(&val, 2, UNICAM_PT_MASK);
> + unicam_set_field(&val, 1, UNICAM_PE);
> + unicam_reg_write(unicam, UNICAM_PRI, val);
> +
> + unicam_reg_write_field(unicam, UNICAM_ANA, 0, UNICAM_DDL);
> +
> + /* Always start in trigger frame capture mode (UNICAM_FCM set) */
> + val = UNICAM_FSIE | UNICAM_FEIE | UNICAM_FCM | UNICAM_IBOB;
> + unicam_set_field(&val, line_int_freq, UNICAM_LCIE_MASK);
> + unicam_reg_write(unicam, UNICAM_ICTL, val);
> + unicam_reg_write(unicam, UNICAM_STA, UNICAM_STA_MASK_ALL);
> + unicam_reg_write(unicam, UNICAM_ISTA, UNICAM_ISTA_MASK_ALL);
> +
> + /* tclk_term_en */
> + unicam_reg_write_field(unicam, UNICAM_CLT, 2, UNICAM_CLT1_MASK);
> + /* tclk_settle */
> + unicam_reg_write_field(unicam, UNICAM_CLT, 6, UNICAM_CLT2_MASK);
> + /* td_term_en */
> + unicam_reg_write_field(unicam, UNICAM_DLT, 2, UNICAM_DLT1_MASK);
> + /* ths_settle */
> + unicam_reg_write_field(unicam, UNICAM_DLT, 6, UNICAM_DLT2_MASK);
> + /* trx_enable */
> + unicam_reg_write_field(unicam, UNICAM_DLT, 0, UNICAM_DLT3_MASK);
> +
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_SOE);
> +
> + /* Packet compare setup - required to avoid missing frame ends */
> + val = 0;
> + unicam_set_field(&val, 1, UNICAM_PCE);
> + unicam_set_field(&val, 1, UNICAM_GI);
> + unicam_set_field(&val, 1, UNICAM_CPH);
> + unicam_set_field(&val, 0, UNICAM_PCVC_MASK);
> + unicam_set_field(&val, 1, UNICAM_PCDT_MASK);
> + unicam_reg_write(unicam, UNICAM_CMP0, val);
> +
> + /* Enable clock lane and set up terminations */
> + val = 0;
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + /* CSI2 */
> + unicam_set_field(&val, 1, UNICAM_CLE);
> + unicam_set_field(&val, 1, UNICAM_CLLPE);
> + if (unicam->bus_flags & V4L2_MBUS_CSI2_CONTINUOUS_CLOCK) {
> + unicam_set_field(&val, 1, UNICAM_CLTRE);
> + unicam_set_field(&val, 1, UNICAM_CLHSE);
> + }
> + } else {
> + /* CCP2 */
> + unicam_set_field(&val, 1, UNICAM_CLE);
> + unicam_set_field(&val, 1, UNICAM_CLHSE);
> + unicam_set_field(&val, 1, UNICAM_CLTRE);
> + }
> + unicam_reg_write(unicam, UNICAM_CLK, val);
> +
> + /*
> + * Enable required data lanes with appropriate terminations.
> + * The same value needs to be written to UNICAM_DATn registers for
> + * the active lanes, and 0 for inactive ones.
> + */
> + val = 0;
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + /* CSI2 */
> + unicam_set_field(&val, 1, UNICAM_DLE);
> + unicam_set_field(&val, 1, UNICAM_DLLPE);
> + if (unicam->bus_flags & V4L2_MBUS_CSI2_CONTINUOUS_CLOCK) {
> + unicam_set_field(&val, 1, UNICAM_DLTRE);
> + unicam_set_field(&val, 1, UNICAM_DLHSE);
> + }
> + } else {
> + /* CCP2 */
> + unicam_set_field(&val, 1, UNICAM_DLE);
> + unicam_set_field(&val, 1, UNICAM_DLHSE);
> + unicam_set_field(&val, 1, UNICAM_DLTRE);
> + }
> + unicam_reg_write(unicam, UNICAM_DAT0, val);
> +
> + if (unicam->active_data_lanes == 1)
> + val = 0;
> + unicam_reg_write(unicam, UNICAM_DAT1, val);
> +
> + if (unicam->max_data_lanes > 2) {
> + /*
> + * Registers UNICAM_DAT2 and UNICAM_DAT3 only valid if the
> + * instance supports more than 2 data lanes.
> + */
> + if (unicam->active_data_lanes == 2)
> + val = 0;
> + unicam_reg_write(unicam, UNICAM_DAT2, val);
> +
> + if (unicam->active_data_lanes == 3)
> + val = 0;
> + unicam_reg_write(unicam, UNICAM_DAT3, val);
> + }
> +
> + unicam_reg_write(unicam, UNICAM_IBLS,
> + node->v_fmt.fmt.pix.bytesperline);
> + size = node->v_fmt.fmt.pix.sizeimage;
> + unicam_wr_dma_addr(&unicam->node[UNICAM_IMAGE_NODE],
> + addr[UNICAM_IMAGE_NODE], size);
> + unicam_set_packing_config(unicam);
> + unicam_cfg_image_id(unicam);
> +
> + val = unicam_reg_read(unicam, UNICAM_MISC);
> + unicam_set_field(&val, 1, UNICAM_FL0);
> + unicam_set_field(&val, 1, UNICAM_FL1);
> + unicam_reg_write(unicam, UNICAM_MISC, val);
> +
> + /* Enable peripheral */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPE);
> +
> + /* Load image pointers */
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_LIP_MASK);
> +
> + /*
> + * Enable trigger only for the first frame to
> + * sync correctly to the FS from the source.
> + */
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_TFC);
> +}
> +
> +static void unicam_start_metadata(struct unicam_device *unicam, dma_addr_t *addr)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_METADATA_NODE];
> + unsigned int size;
> +
> + dev_dbg(unicam->dev, "enable metadata dma\n");
> + size = node->v_fmt.fmt.meta.buffersize;
> + unicam_enable_ed(unicam);
> + unicam_wr_dma_addr(node, addr[UNICAM_METADATA_NODE], size);
> + unicam_reg_write_field(unicam, UNICAM_DCS, 1, UNICAM_LDP);
> +}
> +
> +static void unicam_disable(struct unicam_device *unicam)
> +{
> + /* Analogue lane control disable */
> + unicam_reg_write_field(unicam, UNICAM_ANA, 1, UNICAM_DDL);
> +
> + /* Stop the output engine */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_SOE);
> +
> + /* Disable the data lanes. */
> + unicam_reg_write(unicam, UNICAM_DAT0, 0);
> + unicam_reg_write(unicam, UNICAM_DAT1, 0);
> +
> + if (unicam->max_data_lanes > 2) {
> + unicam_reg_write(unicam, UNICAM_DAT2, 0);
> + unicam_reg_write(unicam, UNICAM_DAT3, 0);
> + }
> +
> + /* Peripheral reset */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPR);
> + usleep_range(50, 100);
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPR);
> +
> + /* Disable peripheral */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPE);
> +
> + /* Clear ED setup */
> + unicam_reg_write(unicam, UNICAM_DCS, 0);
> +
> + /* Disable all lane clocks */
> + unicam_clk_write(unicam, 0);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 subdev Operations
> + */
> +
> +static int __unicam_subdev_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_krouting *routing)
> +{
> + int ret;
> +
> + ret = v4l2_subdev_routing_validate_1_to_1(routing);
> + if (ret)
> + return ret;
> +
> + v4l2_subdev_lock_state(state);
> +
> + ret = v4l2_subdev_set_routing(sd, state, routing);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_subdev_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + enum v4l2_subdev_format_whence which,
> + struct v4l2_subdev_krouting *routing)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> +
> + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && unicam->subdev.streaming)
> + return -EBUSY;
> +
> + return __unicam_subdev_set_routing(sd, state, routing);
> +}
> +
> +static int unicam_subdev_init_cfg(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state)
> +{
> + struct v4l2_subdev_route routes[] = {
> + {
> + .sink_pad = UNICAM_SD_PAD_SINK,
> + .sink_stream = 0,
> + .source_pad = UNICAM_SD_PAD_SOURCE_IMAGE,
> + .source_stream = 0,
> + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> + },
> + };
> +
> + struct v4l2_subdev_krouting routing = {
> + .num_routes = ARRAY_SIZE(routes),
> + .routes = routes,
> + };
> +
> + /* Initialize routing to single route to the fist source pad */
> + return __unicam_subdev_set_routing(sd, state, &routing);
> +}
> +
> +static int unicam_subdev_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + u32 pad, stream;
> + int ret = -EINVAL;
> +
> + v4l2_subdev_lock_state(state);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, code->pad,
> + code->stream, &pad,
> + &stream);
> + if (ret)
> + goto out;
> +
> + if (unicam_sd_pad_is_source(code->pad)) {
> + /* No transcoding, source and sink codes must match. */
> + struct v4l2_mbus_framefmt *fmt;
> +
> + fmt = v4l2_state_get_stream_format(state, pad, stream);
> + if (!fmt)
> + goto out;
> +
> + if (code->index > 0)
> + goto out;
> +
> + code->code = fmt->code;
> + } else {
> + const struct unicam_fmt *formats;
> + unsigned int num_formats;
> +
> + if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + formats = unicam_image_formats;
> + num_formats = ARRAY_SIZE(unicam_image_formats);
> + } else {
> + formats = unicam_meta_formats;
> + num_formats = ARRAY_SIZE(unicam_meta_formats);
> + }
> +
> + if (code->index >= num_formats)
> + goto out;
> +
> + code->code = formats[code->index].code;
> + }
> +
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_sd_enable_streams(struct v4l2_subdev *sd, u32 pad,
> + u64 streams_mask)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_subdev_state *state;
> + u32 other_pad, other_stream;
> + int ret;
> +
> + if (WARN_ON(streams_mask != 1))
> + return -EINVAL;
> +
> + state = v4l2_subdev_lock_active_state(sd);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
> + &other_pad, &other_stream);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + if (ret)
> + return ret;
> +
> + unicam->sequence = 0;
> +
> + dev_dbg(unicam->dev, "Running with %u data lanes\n",
> + unicam->active_data_lanes);
> +
> + ret = clk_set_min_rate(unicam->vpu_clock, UNICAM_MIN_VPU_CLOCK_RATE);
> + if (ret) {
> + dev_err(unicam->dev, "failed to set up VPU clock\n");
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(unicam->vpu_clock);
> + if (ret) {
> + dev_err(unicam->dev, "Failed to enable VPU clock: %d\n", ret);
> + goto err_vpu_clock;
> + }
> +
> + ret = clk_set_rate(unicam->clock, 100 * 1000 * 1000);
> + if (ret) {
> + dev_err(unicam->dev, "failed to set up CSI clock\n");
> + goto err_vpu_prepare;
> + }
> +
> + ret = clk_prepare_enable(unicam->clock);
> + if (ret) {
> + dev_err(unicam->dev, "Failed to enable CSI clock: %d\n", ret);
> + goto err_vpu_prepare;
> + }
> +
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, enable_streams,
> + unicam->sensor.pad->index, BIT(other_stream));
> +
> + if (ret && ret == -ENOIOCTLCMD)
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, s_stream, 1);
> +
> + if (ret) {
> + dev_err(unicam->dev, "stream on failed in subdev\n");
> + goto err_enable_stream;
> + }
> +
> + unicam->subdev.streaming = true;
> +
> + return 0;
> +
> +err_enable_stream:
> + clk_disable_unprepare(unicam->clock);
> +err_vpu_prepare:
> + clk_disable_unprepare(unicam->vpu_clock);
> +err_vpu_clock:
> + if (clk_set_min_rate(unicam->vpu_clock, 0))
> + dev_err(unicam->dev, "failed to reset the VPU clock\n");
> + return ret;
> +}
> +
> +static int unicam_sd_disable_streams(struct v4l2_subdev *sd, u32 pad,
> + u64 streams_mask)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_subdev_state *state;
> + u32 other_pad, other_stream;
> + int ret;
> +
> + if (WARN_ON(streams_mask != 1))
> + return -EINVAL;
> +
> + state = v4l2_subdev_lock_active_state(sd);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
> + &other_pad, &other_stream);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + if (ret) {
> + dev_err(unicam->dev, "disable streams failed: %d\n", ret);
> + return ret;
> + }
> +
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, disable_streams,
> + unicam->sensor.pad->index, BIT(other_stream));
> +
> + if (ret && ret == -ENOIOCTLCMD) {
> + /* The sensor does not support disable streams. */
> + if (unicam->node[UNICAM_IMAGE_NODE].streaming &&
> + other_pad == unicam->node[UNICAM_METADATA_NODE].pad.index) {
> + /* We won't stop the sensor yet. */
> + unicam->subdev.streaming = false;
> + return 0;
> + }
> + /* There is no more the metadata node, we can stop. */
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, s_stream, 0);
> + }
> +
> + clk_disable_unprepare(unicam->clock);
> + if (clk_set_min_rate(unicam->vpu_clock, 0))
> + dev_err(unicam->dev, "failed to reset the VPU clock\n");
> + clk_disable_unprepare(unicam->vpu_clock);
> +
> + unicam->subdev.streaming = false;
> +
> + return 0;
> +}
> +
> +static int unicam_subdev_set_pad_format(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_format *format)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_mbus_framefmt *sink_format, *source_format;
> + const struct unicam_fmt *fmtinfo;
> + int ret = 0;
> +
> + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && unicam->subdev.streaming)
> + return -EBUSY;
> +
> + /* No transcoding, source and sink formats must match. */
> + if (unicam_sd_pad_is_source(format->pad))
> + return v4l2_subdev_get_fmt(sd, state, format);
> +
> + fmtinfo = unicam_find_format_by_code(format->format.code, format->pad);
> + if (!fmtinfo)
> + fmtinfo = &unicam_image_formats[0];
> +
> + if (format->pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + format->format.width = clamp_t(unsigned int,
> + format->format.width,
> + UNICAM_MIN_WIDTH,
> + UNICAM_MAX_WIDTH);
> + format->format.height = clamp_t(unsigned int,
> + format->format.height,
> + UNICAM_MIN_HEIGHT,
> + UNICAM_MAX_HEIGHT);
> + }
> + format->format.field = V4L2_FIELD_NONE;
> +
> + v4l2_subdev_lock_state(state);
> +
> + sink_format = v4l2_state_get_stream_format(state, format->pad,
> + format->stream);
> + source_format = v4l2_subdev_state_get_opposite_stream_format(state,
> + format->pad,
> + format->stream);
> + if (!sink_format || !source_format) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + *sink_format = format->format;
> + *source_format = format->format;
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_subdev_enum_frame_size(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_frame_size_enum *fse)
> +{
> + const struct unicam_fmt *fmtinfo;
> + u32 pad, stream;
> + int ret = -EINVAL;
> +
> + if (fse->index > 0)
> + return ret;
> +
> + v4l2_subdev_lock_state(state);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, fse->pad,
> + fse->stream, &pad,
> + &stream);
> + if (ret)
> + goto out;
> +
> + if (unicam_sd_pad_is_source(fse->pad)) {
> + /* No transcoding, source and sink formats must match. */
> + struct v4l2_mbus_framefmt *fmt;
> +
> + fmt = v4l2_state_get_stream_format(state, pad, stream);
> + if (!fmt)
> + goto out;
> +
> + if (fse->code != fmt->code)
> + goto out;
> +
> + fse->min_width = fmt->width;
> + fse->max_width = fmt->width;
> + fse->min_height = fmt->height;
> + fse->max_height = fmt->height;
> + } else {
> + fmtinfo = unicam_find_format_by_code(fse->code, pad);
> + if (!fmtinfo)
> + goto out;
> +
> + fse->min_width = UNICAM_MIN_WIDTH;
> + fse->max_width = UNICAM_MAX_WIDTH;
> + fse->min_height = UNICAM_MIN_HEIGHT;
> + fse->max_height = UNICAM_MAX_HEIGHT;
> + }
> +
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static const struct v4l2_subdev_video_ops unicam_subdev_video_ops = {
> + .enable_streams = unicam_sd_enable_streams,
> + .disable_streams = unicam_sd_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_pad_ops unicam_subdev_pad_ops = {
> + .init_cfg = unicam_subdev_init_cfg,
> + .enum_mbus_code = unicam_subdev_enum_mbus_code,
> + .get_fmt = v4l2_subdev_get_fmt,
> + .set_fmt = unicam_subdev_set_pad_format,
> + .set_routing = unicam_subdev_set_routing,
> + .enum_frame_size = unicam_subdev_enum_frame_size,
> +};
> +
> +static const struct v4l2_subdev_ops unicam_subdev_ops = {
> + .video = &unicam_subdev_video_ops,
> + .pad = &unicam_subdev_pad_ops,
> +};
> +
> +static const struct media_entity_operations unicam_subdev_media_ops = {
> + .link_validate = v4l2_subdev_link_validate,
> + .has_route = v4l2_subdev_has_route,
> +};
> +
> +static int unicam_init_and_register_subdev(struct unicam_device *unicam)
> +{
> + struct v4l2_subdev *sd = &unicam->subdev.sd;
> + int ret;
> +
> + v4l2_subdev_init(sd, &unicam_subdev_ops);
> + v4l2_set_subdevdata(sd, unicam);
> + unicam->subdev.sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> + unicam->subdev.sd.entity.ops = &unicam_subdev_media_ops;
> + unicam->subdev.sd.dev = unicam->dev;
> + unicam->subdev.sd.owner = THIS_MODULE;
> + unicam->subdev.sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_MULTIPLEXED;
> + snprintf(unicam->subdev.sd.name, sizeof(unicam->subdev.sd.name), "unicam-subdev");
> +
> + unicam->subdev.pads[UNICAM_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +
> + unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_IMAGE].flags = MEDIA_PAD_FL_SOURCE;
> + unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_METADATA].flags = MEDIA_PAD_FL_SOURCE;
> +
> + ret = media_entity_pads_init(&sd->entity,
> + ARRAY_SIZE(unicam->subdev.pads), unicam->subdev.pads);
> + if (ret) {
> + dev_err(unicam->dev, "Could not init media entity");
> + return ret;
> + }
> +
> + ret = v4l2_subdev_init_finalize(sd);
> + if (ret) {
> + dev_err(unicam->dev, "Could not finalize init for subdev");
> + return ret;
> + }
> +
> + return v4l2_device_register_subdev(&unicam->v4l2_dev, sd);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Videobuf2 Queue Operations
> + */
> +
> +static int unicam_queue_setup(struct vb2_queue *vq,
> + unsigned int *nbuffers,
> + unsigned int *nplanes,
> + unsigned int sizes[],
> + struct device *alloc_devs[])
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vq);
> + struct unicam_device *unicam = node->dev;
> + unsigned int size = is_image_node(node) ?
> + node->v_fmt.fmt.pix.sizeimage :
> + node->v_fmt.fmt.meta.buffersize;
> +
> + if (vq->num_buffers + *nbuffers < 3)
> + *nbuffers = 3 - vq->num_buffers;
> +
> + if (*nplanes) {
> + if (sizes[0] < size) {
> + dev_dbg(unicam->dev, "sizes[0] %i < size %u\n", sizes[0],
> + size);
> + return -EINVAL;
> + }
> + size = sizes[0];
> + }
> +
> + *nplanes = 1;
> + sizes[0] = size;
> +
> + return 0;
> +}
> +
> +static int unicam_buffer_prepare(struct vb2_buffer *vb)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
> + struct unicam_device *unicam = node->dev;
> + struct unicam_buffer *buf = to_unicam_buffer(vb);
> + unsigned long size;
> +
> + if (WARN_ON(!node->fmt))
> + return -EINVAL;
> +
> + size = is_image_node(node) ? node->v_fmt.fmt.pix.sizeimage :
> + node->v_fmt.fmt.meta.buffersize;
> + if (vb2_plane_size(vb, 0) < size) {
> + dev_dbg(unicam->dev, "data will not fit into plane (%lu < %lu)\n",
> + vb2_plane_size(vb, 0), size);
> + return -EINVAL;
> + }
> +
> + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
> + return 0;
> +}
> +
> +static void unicam_buffer_queue(struct vb2_buffer *vb)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
> + struct unicam_buffer *buf = to_unicam_buffer(vb);
> +
> + spin_lock_irq(&node->dma_queue_lock);
> + list_add_tail(&buf->list, &node->dma_queue);
> + spin_unlock_irq(&node->dma_queue_lock);
> +}
> +
> +static void unicam_return_buffers(struct unicam_node *node,
> + enum vb2_buffer_state state)
> +{
> + struct unicam_buffer *buf, *tmp;
> +
> + list_for_each_entry_safe(buf, tmp, &node->dma_queue, list) {
> + list_del(&buf->list);
> + vb2_buffer_done(&buf->vb.vb2_buf, state);
> + }
> +
> + if (node->cur_frm)
> + vb2_buffer_done(&node->cur_frm->vb.vb2_buf,
> + state);
> + if (node->next_frm && node->cur_frm != node->next_frm)
> + vb2_buffer_done(&node->next_frm->vb.vb2_buf,
> + state);
> +
> + node->cur_frm = NULL;
> + node->next_frm = NULL;
> +}
> +
> +static int unicam_video_check_format(struct unicam_node *node)
> +{
> + const struct v4l2_mbus_framefmt *format;
> + struct unicam_device *unicam = node->dev;
> + struct v4l2_subdev_state *state;
> + int ret = 0;
> +
> + state = v4l2_subdev_lock_active_state(&unicam->subdev.sd);
> +
> + format = v4l2_state_get_stream_format(state, UNICAM_SD_PAD_SOURCE_IMAGE, 0);
> + if (!format) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + if (node->fmt->code != format->code ||
> + node->v_fmt.fmt.pix.height != format->height ||
> + node->v_fmt.fmt.pix.width != format->width ||
> + node->v_fmt.fmt.pix.field != format->field) {
> + dev_dbg(unicam->dev, "(%u x %u) %08x %s != (%u x %u) %08x %s\n",
> + node->v_fmt.fmt.pix.width, node->v_fmt.fmt.pix.height,
> + node->fmt->code,
> + v4l2_field_names[node->v_fmt.fmt.pix.field],
> + format->width, format->height, format->code,
> + v4l2_field_names[format->field]);
> + ret = -EPIPE;
> + goto out;
> + }
> +
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_start_streaming(struct vb2_queue *vq, unsigned int count)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vq);
> + struct unicam_device *unicam = node->dev;
> + dma_addr_t buffer_addr[UNICAM_MAX_NODES] = { 0 };
> + struct unicam_buffer *buf;
> + struct v4l2_subdev_state *state;
> + unsigned long flags;
> + unsigned int i;
> + int ret;
> + u32 pad, stream;
> + u32 remote_pad_index = is_image_node(node) ? UNICAM_SD_PAD_SOURCE_IMAGE
> + : UNICAM_SD_PAD_SOURCE_METADATA;
> +
> + /* Look for the route for the given pad and stream. */
> + state = v4l2_subdev_lock_active_state(&unicam->subdev.sd);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
> + remote_pad_index, 0,
> + &pad, &stream);
> + if (ret)
> + goto err_streaming;
> +
> + v4l2_subdev_unlock_state(state);
> +
> + dev_dbg(unicam->dev, "Starting stream 0 on pad %d on subdev %s\n",
> + remote_pad_index, unicam->subdev.sd.name);
> +
> + /* The metadata node can't be started alone. */
> + if (is_metadata_node(node)) {
> + if (!unicam->node[UNICAM_IMAGE_NODE].streaming) {
> + dev_err(unicam->dev,
> + "Can't start metadata without image\n");
> + return -EINVAL;
> + }
> + dev_dbg(unicam->dev, "starting metadata node\n");
> +
> + spin_lock_irqsave(&node->dma_queue_lock, flags);
> + buf = list_first_entry(&node->dma_queue,
> + struct unicam_buffer, list);
> + dev_dbg(unicam->dev, "buffer %d: %p\n", i, buf);
> + node->cur_frm = buf;
> + node->next_frm = buf;
> + list_del(&buf->list);
> + spin_unlock_irqrestore(&node->dma_queue_lock, flags);
> +
> + buffer_addr[UNICAM_METADATA_NODE] =
> + vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> + dev_dbg(unicam->dev, "buffer %d addr: %lld\n", i, buffer_addr[i]);

i tried to compile the driver and get the following warning. I think the
easiest solution would be to drop this debug message.

In file included from ./include/linux/printk.h:555:0,

                 from ./include/linux/kernel.h:29,
                 from ./include/linux/clk.h:13,
                 from drivers/media/platform/bcm2835/bcm2835-unicam.c:47:
drivers/media/platform/bcm2835/bcm2835-unicam.c: In function
‘unicam_start_streaming’:
drivers/media/platform/bcm2835/bcm2835-unicam.c:1676:24: warning: format
‘%lld’ expects argument of type ‘long long int’, but argument 5 has type
‘dma_addr_t {aka unsigned int}’ [-Wformat=]
   dev_dbg(unicam->dev, "buffer %d addr: %lld\n", i, buffer_addr[i]);
                        ^
./include/linux/dynamic_debug.h:134:15: note: in definition of macro
‘__dynamic_func_call’
   func(&id, ##__VA_ARGS__);  \
               ^~~~~~~~~~~
./include/linux/dynamic_debug.h:166:2: note: in expansion of macro
‘_dynamic_func_call’
  _dynamic_func_call(fmt,__dynamic_dev_dbg,   \
  ^~~~~~~~~~~~~~~~~~
./include/linux/dev_printk.h:155:2: note: in expansion of macro
‘dynamic_dev_dbg’
  dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__)
  ^~~~~~~~~~~~~~~
./include/linux/dev_printk.h:155:23: note: in expansion of macro ‘dev_fmt’
  dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__)
                       ^~~~~~~
drivers/media/platform/bcm2835/bcm2835-unicam.c:1676:3: note: in
expansion of macro ‘dev_dbg’
   dev_dbg(unicam->dev, "buffer %d addr: %lld\n", i, buffer_addr[i]);
   ^~~~~~~

2022-02-09 13:22:28

by Jean-Michel Hautbois

[permalink] [raw]
Subject: [PATCH v5 05/11] media: MAINTAINERS: add bcm2835 unicam driver

Add the BCM2835 Camera driver named Unicam in the list.

Signed-off-by: Jean-Michel Hautbois <[email protected]>
---
MAINTAINERS | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index c25bc24d58e2..ca84a66c6a9a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3670,6 +3670,14 @@ N: bcm113*
N: bcm216*
N: kona

+BROADCOM BCM2835 CAMERA DRIVER
+M: Raspberry Pi Kernel Maintenance <[email protected]>
+L: [email protected]
+S: Maintained
+F: Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml
+F: arch/arm/boot/dts/bcm283x*
+F: drivers/media/platform/bcm2835/
+
BROADCOM BCM47XX MIPS ARCHITECTURE
M: Hauke Mehrtens <[email protected]>
M: RafaÅ‚ MiÅ‚ecki <[email protected]>
--
2.32.0


2022-02-13 18:56:10

by Stefan Wahren

[permalink] [raw]
Subject: Re: [PATCH v5 06/11] ARM: dts: bcm2711: Add unicam CSI nodes

Hi Jean-Michel,

Am 08.02.22 um 16:50 schrieb Jean-Michel Hautbois:
> Add both MIPI CSI-2 nodes in the core bcm2711 tree. Use the 3-cells
> interrupt declaration, corresponding clocks and default as disabled.
>
> Signed-off-by: Jean-Michel Hautbois <[email protected]>
> Reviewed-by: Laurent Pinchart <[email protected]>
> ---
> arch/arm/boot/dts/bcm2711-rpi.dtsi | 15 +++++++++++++++
> arch/arm/boot/dts/bcm2711.dtsi | 22 ++++++++++++++++++++++
> 2 files changed, 37 insertions(+)
>
> diff --git a/arch/arm/boot/dts/bcm2711-rpi.dtsi b/arch/arm/boot/dts/bcm2711-rpi.dtsi
> index ca266c5d9f9b..97ee494891af 100644
> --- a/arch/arm/boot/dts/bcm2711-rpi.dtsi
> +++ b/arch/arm/boot/dts/bcm2711-rpi.dtsi
> @@ -1,6 +1,7 @@
> // SPDX-License-Identifier: GPL-2.0
> #include "bcm2835-rpi.dtsi"
>
> +#include <dt-bindings/power/raspberrypi-power.h>
> #include <dt-bindings/reset/raspberrypi,firmware-reset.h>
>
> / {
> @@ -18,6 +19,20 @@ aliases {
> };
> };
>
> +&csi0 {
> + clocks = <&clocks BCM2835_CLOCK_CAM0>,
> + <&firmware_clocks 4>;
> + clock-names = "lp", "vpu";
> + power-domains = <&power RPI_POWER_DOMAIN_UNICAM0>;
> +};
> +
> +&csi1 {
> + clocks = <&clocks BCM2835_CLOCK_CAM1>,
> + <&firmware_clocks 4>;
> + clock-names = "lp", "vpu";
> + power-domains = <&power RPI_POWER_DOMAIN_UNICAM1>;
> +};
see comment below
> +
> &firmware {
> firmware_clocks: clocks {
> compatible = "raspberrypi,firmware-clocks";
> diff --git a/arch/arm/boot/dts/bcm2711.dtsi b/arch/arm/boot/dts/bcm2711.dtsi
> index dff18fc9a906..21eb10122e50 100644
> --- a/arch/arm/boot/dts/bcm2711.dtsi
> +++ b/arch/arm/boot/dts/bcm2711.dtsi
> @@ -293,6 +293,28 @@ hvs: hvs@7e400000 {
> interrupts = <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>;
> };
>
> + csi0: csi@7e800000 {
> + compatible = "brcm,bcm2835-unicam";
> + reg = <0x7e800000 0x800>,
> + <0x7e802000 0x4>;
> + reg-names = "unicam", "cmi";
> + interrupts = <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>;
> + status = "disabled";
> + port {
> + };
> + };
> +
> + csi1: csi@7e801000 {
> + compatible = "brcm,bcm2835-unicam";
> + reg = <0x7e801000 0x800>,
> + <0x7e802004 0x4>;
> + reg-names = "unicam", "cmi";
> + interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
> + status = "disabled";
> + port {
> + };
> + };
> +

i didn't noticed before that everything except of the interrupts are
identical to the bcm283x boards [1]. So please define both nodes in
bcm283x.dtsi (with bcm283x interrupts) and just override the interrupts
for bcm2711 in this dtsi file.

As a result the clocks and power domains from above can go to
bcm2835-rpi.dtsi.

> pixelvalve3: pixelvalve@7ec12000 {
> compatible = "brcm,bcm2711-pixelvalve3";
> reg = <0x7ec12000 0x100>;

2022-02-14 03:01:09

by Stefan Wahren

[permalink] [raw]
Subject: Re: [PATCH v5 06/11] ARM: dts: bcm2711: Add unicam CSI nodes

Am 13.02.22 um 11:35 schrieb Stefan Wahren:
> Hi Jean-Michel,
>
> Am 08.02.22 um 16:50 schrieb Jean-Michel Hautbois:
>> +
>> &firmware {
>> firmware_clocks: clocks {
>> compatible = "raspberrypi,firmware-clocks";
>> diff --git a/arch/arm/boot/dts/bcm2711.dtsi b/arch/arm/boot/dts/bcm2711.dtsi
>> index dff18fc9a906..21eb10122e50 100644
>> --- a/arch/arm/boot/dts/bcm2711.dtsi
>> +++ b/arch/arm/boot/dts/bcm2711.dtsi
>> @@ -293,6 +293,28 @@ hvs: hvs@7e400000 {
>> interrupts = <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>;
>> };
>>
>> + csi0: csi@7e800000 {
>> + compatible = "brcm,bcm2835-unicam";
>> + reg = <0x7e800000 0x800>,
>> + <0x7e802000 0x4>;
>> + reg-names = "unicam", "cmi";
>> + interrupts = <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>;
>> + status = "disabled";
>> + port {
>> + };
>> + };
>> +
>> + csi1: csi@7e801000 {
>> + compatible = "brcm,bcm2835-unicam";
>> + reg = <0x7e801000 0x800>,
>> + <0x7e802004 0x4>;
>> + reg-names = "unicam", "cmi";
>> + interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
>> + status = "disabled";
>> + port {
>> + };
>> + };
>> +
> i didn't noticed before that everything except of the interrupts are
> identical to the bcm283x boards [1]. So please define both nodes in
> bcm283x.dtsi (with bcm283x interrupts) and just override the interrupts
> for bcm2711 in this dtsi file.
>
> As a result the clocks and power domains from above can go to
> bcm2835-rpi.dtsi.

sorry, i forgot the downstream link:

[1] -
https://github.com/raspberrypi/linux/blob/rpi-5.15.y/arch/arm/boot/dts/bcm270x.dtsi#L88

>
>> pixelvalve3: pixelvalve@7ec12000 {
>> compatible = "brcm,bcm2711-pixelvalve3";
>> reg = <0x7ec12000 0x100>;

2022-02-14 03:37:02

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

On Tue, Feb 08, 2022 at 10:00:23PM +0100, Stefan Wahren wrote:
> Hi Jean-Michel,
>
> Am 08.02.22 um 16:50 schrieb Jean-Michel Hautbois:
> > Add driver for the Unicam camera receiver block on BCM283x processors.
> > It is represented as two video device nodes: unicam-image and
> > unicam-embedded which are connected to an internal subdev (named
> > unicam-subdev) in order to manage streams routing.
> >
> > Signed-off-by: Dave Stevenson <[email protected]>
> > Signed-off-by: Naushir Patuck <[email protected]>
> > Signed-off-by: Jean-Michel Hautbois <[email protected]>
> >
> > ---
> > v4:
> > - Add the vendor prefox for DT name
> > - Use the reg-names in DT parsing
> > - Remove MAINTAINERS entry
> >
> > v3 main changes:
> > - Change code organization
> > - Remove unused variables
> > - Correct the fmt_meta functions
> > - Rewrite the start/stop streaming
> > - You can now start the image node alone, but not the metadata one
> > - The buffers are allocated per-node
> > - only the required stream is started, if the route exists and is
> > enabled
> > - Prefix the macros with UNICAM_ to not have too generic names
> > - Drop colorspace support
> > -> This is causing issues in the try-fmt v4l2-compliance test
> > test VIDIOC_G_FMT: OK
> > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > test VIDIOC_TRY_FMT: FAIL
> > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > test VIDIOC_S_FMT: FAIL
> >
> > v2: Remove the unicam_{info,debug,error} macros and use
> > dev_dbg/dev_err instead.
> > ---
> > drivers/media/platform/Kconfig | 1 +
> > drivers/media/platform/Makefile | 2 +
> > drivers/media/platform/bcm2835/Kconfig | 21 +
> > drivers/media/platform/bcm2835/Makefile | 3 +
> > .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> > .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> > 6 files changed, 2850 insertions(+)
> > create mode 100644 drivers/media/platform/bcm2835/Kconfig
> > create mode 100644 drivers/media/platform/bcm2835/Makefile
> > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c

[snip]

> > diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam.c b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> > new file mode 100644
> > index 000000000000..470e691637c7
> > --- /dev/null
> > +++ b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> > @@ -0,0 +1,2570 @@

[snip]

> > +static int unicam_start_streaming(struct vb2_queue *vq, unsigned int count)
> > +{
> > + struct unicam_node *node = vb2_get_drv_priv(vq);
> > + struct unicam_device *unicam = node->dev;
> > + dma_addr_t buffer_addr[UNICAM_MAX_NODES] = { 0 };
> > + struct unicam_buffer *buf;
> > + struct v4l2_subdev_state *state;
> > + unsigned long flags;
> > + unsigned int i;
> > + int ret;
> > + u32 pad, stream;
> > + u32 remote_pad_index = is_image_node(node) ? UNICAM_SD_PAD_SOURCE_IMAGE
> > + : UNICAM_SD_PAD_SOURCE_METADATA;
> > +
> > + /* Look for the route for the given pad and stream. */
> > + state = v4l2_subdev_lock_active_state(&unicam->subdev.sd);
> > +
> > + ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
> > + remote_pad_index, 0,
> > + &pad, &stream);
> > + if (ret)
> > + goto err_streaming;
> > +
> > + v4l2_subdev_unlock_state(state);
> > +
> > + dev_dbg(unicam->dev, "Starting stream 0 on pad %d on subdev %s\n",
> > + remote_pad_index, unicam->subdev.sd.name);
> > +
> > + /* The metadata node can't be started alone. */
> > + if (is_metadata_node(node)) {
> > + if (!unicam->node[UNICAM_IMAGE_NODE].streaming) {
> > + dev_err(unicam->dev,
> > + "Can't start metadata without image\n");
> > + return -EINVAL;
> > + }
> > + dev_dbg(unicam->dev, "starting metadata node\n");
> > +
> > + spin_lock_irqsave(&node->dma_queue_lock, flags);
> > + buf = list_first_entry(&node->dma_queue,
> > + struct unicam_buffer, list);
> > + dev_dbg(unicam->dev, "buffer %d: %p\n", i, buf);
> > + node->cur_frm = buf;
> > + node->next_frm = buf;
> > + list_del(&buf->list);
> > + spin_unlock_irqrestore(&node->dma_queue_lock, flags);
> > +
> > + buffer_addr[UNICAM_METADATA_NODE] =
> > + vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> > + dev_dbg(unicam->dev, "buffer %d addr: %lld\n", i, buffer_addr[i]);
>
> i tried to compile the driver and get the following warning. I think the
> easiest solution would be to drop this debug message.

For the record, the right format specifier would be

dev_dbg(unicam->dev, "buffer %d addr: %pad\n", i, &buffer_addr[i]);

(note the & before buffer_addr)

> In file included from ./include/linux/printk.h:555:0,
>
>                  from ./include/linux/kernel.h:29,
>                  from ./include/linux/clk.h:13,
>                  from drivers/media/platform/bcm2835/bcm2835-unicam.c:47:
> drivers/media/platform/bcm2835/bcm2835-unicam.c: In function
> ‘unicam_start_streaming’:
> drivers/media/platform/bcm2835/bcm2835-unicam.c:1676:24: warning: format
> ‘%lld’ expects argument of type ‘long long int’, but argument 5 has type
> ‘dma_addr_t {aka unsigned int}’ [-Wformat=]
>    dev_dbg(unicam->dev, "buffer %d addr: %lld\n", i, buffer_addr[i]);
>                         ^
> ./include/linux/dynamic_debug.h:134:15: note: in definition of macro
> ‘__dynamic_func_call’
>    func(&id, ##__VA_ARGS__);  \
>                ^~~~~~~~~~~
> ./include/linux/dynamic_debug.h:166:2: note: in expansion of macro
> ‘_dynamic_func_call’
>   _dynamic_func_call(fmt,__dynamic_dev_dbg,   \
>   ^~~~~~~~~~~~~~~~~~
> ./include/linux/dev_printk.h:155:2: note: in expansion of macro
> ‘dynamic_dev_dbg’
>   dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__)
>   ^~~~~~~~~~~~~~~
> ./include/linux/dev_printk.h:155:23: note: in expansion of macro ‘dev_fmt’
>   dynamic_dev_dbg(dev, dev_fmt(fmt), ##__VA_ARGS__)
>                        ^~~~~~~
> drivers/media/platform/bcm2835/bcm2835-unicam.c:1676:3: note: in
> expansion of macro ‘dev_dbg’
>    dev_dbg(unicam->dev, "buffer %d addr: %lld\n", i, buffer_addr[i]);
>    ^~~~~~~

--
Regards,

Laurent Pinchart

2022-02-14 07:10:35

by Stefan Wahren

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Hi Jean-Michel,

Am 08.02.22 um 16:50 schrieb Jean-Michel Hautbois:
> Add driver for the Unicam camera receiver block on BCM283x processors.
> It is represented as two video device nodes: unicam-image and
> unicam-embedded which are connected to an internal subdev (named
> unicam-subdev) in order to manage streams routing.
>
> Signed-off-by: Dave Stevenson <[email protected]>
> Signed-off-by: Naushir Patuck <[email protected]>
> Signed-off-by: Jean-Michel Hautbois <[email protected]>
>
> ---
> v4:
> - Add the vendor prefox for DT name
> - Use the reg-names in DT parsing
> - Remove MAINTAINERS entry
>
> v3 main changes:
> - Change code organization
> - Remove unused variables
> - Correct the fmt_meta functions
> - Rewrite the start/stop streaming
> - You can now start the image node alone, but not the metadata one
> - The buffers are allocated per-node
> - only the required stream is started, if the route exists and is
> enabled
> - Prefix the macros with UNICAM_ to not have too generic names
> - Drop colorspace support
> -> This is causing issues in the try-fmt v4l2-compliance test
> test VIDIOC_G_FMT: OK
> fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> test VIDIOC_TRY_FMT: FAIL
> fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> test VIDIOC_S_FMT: FAIL
>
> v2: Remove the unicam_{info,debug,error} macros and use
> dev_dbg/dev_err instead.
> ---
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 2 +
> drivers/media/platform/bcm2835/Kconfig | 21 +
> drivers/media/platform/bcm2835/Makefile | 3 +
> .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> 6 files changed, 2850 insertions(+)
> create mode 100644 drivers/media/platform/bcm2835/Kconfig
> create mode 100644 drivers/media/platform/bcm2835/Makefile
> create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c
>
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 9fbdba0fd1e7..033b0358fbb8 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -170,6 +170,7 @@ source "drivers/media/platform/am437x/Kconfig"
> source "drivers/media/platform/xilinx/Kconfig"
> source "drivers/media/platform/rcar-vin/Kconfig"
> source "drivers/media/platform/atmel/Kconfig"
> +source "drivers/media/platform/bcm2835/Kconfig"
> source "drivers/media/platform/sunxi/Kconfig"
>
> config VIDEO_TI_CAL
> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> index 28eb4aadbf45..18894fc586aa 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -86,6 +86,8 @@ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom/camss/
>
> obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
>
> +obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835/
> +
> obj-y += sunxi/
>
> obj-$(CONFIG_VIDEO_MESON_GE2D) += meson/ge2d/
> diff --git a/drivers/media/platform/bcm2835/Kconfig b/drivers/media/platform/bcm2835/Kconfig
> new file mode 100644
> index 000000000000..1691541da905
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/Kconfig
> @@ -0,0 +1,21 @@
> +# Broadcom VideoCore4 V4L2 camera support
> +
> +config VIDEO_BCM2835_UNICAM
> + tristate "Broadcom BCM283x/BCM271x Unicam video capture driver"
> + depends on ARCH_BCM2835 || COMPILE_TEST
> + depends on VIDEO_V4L2
> + select MEDIA_CONTROLLER
> + select VIDEO_V4L2_SUBDEV_API
> + select V4L2_FWNODE
> + select VIDEOBUF2_DMA_CONTIG
> + help
> + Say Y here to enable support for the BCM283x/BCM271x CSI-2 receiver.
> + This is a V4L2 driver that controls the CSI-2 receiver directly,
> + independently from the VC4 firmware.
> + This driver is mutually exclusive with the use of bcm2835-camera. The
> + firmware will disable all access to the peripheral from within the
> + firmware if it finds a DT node using it, and bcm2835-camera will
> + therefore fail to probe.
would be nice to know, what is the exact condition for the firmware. The
compatible and must its status be enabled?
> +
> + To compile this driver as a module, choose M here. The module will be
> + called bcm2835-unicam.
> diff --git a/drivers/media/platform/bcm2835/Makefile b/drivers/media/platform/bcm2835/Makefile
> new file mode 100644
> index 000000000000..a98aba03598a
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/Makefile
> @@ -0,0 +1,3 @@
> +# Makefile for BCM2835 Unicam driver
> +
> +obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835-unicam.o
> diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> new file mode 100644
> index 000000000000..b8d297076a02
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> @@ -0,0 +1,253 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +
> +/*
> + * Copyright (C) 2017-2020 Raspberry Pi Trading.
> + * Dave Stevenson <[email protected]>
> + */
> +
> +#ifndef VC4_REGS_UNICAM_H
> +#define VC4_REGS_UNICAM_H
> +
> +/*
> + * The following values are taken from files found within the code drop
> + * made by Broadcom for the BCM21553 Graphics Driver, predominantly in
> + * brcm_usrlib/dag/vmcsx/vcinclude/hardware_vc4.h.
> + * They have been modified to be only the register offset.
> + */
> +#define UNICAM_CTRL 0x000
> +#define UNICAM_STA 0x004
> +#define UNICAM_ANA 0x008
> +#define UNICAM_PRI 0x00c
> +#define UNICAM_CLK 0x010
> +#define UNICAM_CLT 0x014
> +#define UNICAM_DAT0 0x018
> +#define UNICAM_DAT1 0x01c
> +#define UNICAM_DAT2 0x020
> +#define UNICAM_DAT3 0x024
> +#define UNICAM_DLT 0x028
> +#define UNICAM_CMP0 0x02c
> +#define UNICAM_CMP1 0x030
> +#define UNICAM_CAP0 0x034
> +#define UNICAM_CAP1 0x038
> +#define UNICAM_ICTL 0x100
> +#define UNICAM_ISTA 0x104
> +#define UNICAM_IDI0 0x108
> +#define UNICAM_IPIPE 0x10c
> +#define UNICAM_IBSA0 0x110
> +#define UNICAM_IBEA0 0x114
> +#define UNICAM_IBLS 0x118
> +#define UNICAM_IBWP 0x11c
> +#define UNICAM_IHWIN 0x120
> +#define UNICAM_IHSTA 0x124
> +#define UNICAM_IVWIN 0x128
> +#define UNICAM_IVSTA 0x12c
> +#define UNICAM_ICC 0x130
> +#define UNICAM_ICS 0x134
> +#define UNICAM_IDC 0x138
> +#define UNICAM_IDPO 0x13c
> +#define UNICAM_IDCA 0x140
> +#define UNICAM_IDCD 0x144
> +#define UNICAM_IDS 0x148
> +#define UNICAM_DCS 0x200
> +#define UNICAM_DBSA0 0x204
> +#define UNICAM_DBEA0 0x208
> +#define UNICAM_DBWP 0x20c
> +#define UNICAM_DBCTL 0x300
> +#define UNICAM_IBSA1 0x304
> +#define UNICAM_IBEA1 0x308
> +#define UNICAM_IDI1 0x30c
> +#define UNICAM_DBSA1 0x310
> +#define UNICAM_DBEA1 0x314
> +#define UNICAM_MISC 0x400
> +
> +/*
> + * The following bitmasks are from the kernel released by Broadcom
> + * for Android - https://android.googlesource.com/kernel/bcm/
> + * The Rhea, Hawaii, and Java chips all contain the same VideoCore4
> + * Unicam block as BCM2835, as defined in eg
> + * arch/arm/mach-rhea/include/mach/rdb_A0/brcm_rdb_cam.h and similar.
> + * Values reworked to use the kernel BIT and GENMASK macros.
> + *
> + * Some of the bit mnenomics have been amended to match the datasheet.
> + */
> +/* UNICAM_CTRL Register */
> +#define UNICAM_CPE BIT(0)
> +#define UNICAM_MEM BIT(1)
> +#define UNICAM_CPR BIT(2)
> +#define UNICAM_CPM_MASK GENMASK(3, 3)
> +#define UNICAM_CPM_CSI2 0
> +#define UNICAM_CPM_CCP2 1
> +#define UNICAM_SOE BIT(4)
> +#define UNICAM_DCM_MASK GENMASK(5, 5)
> +#define UNICAM_DCM_STROBE 0
> +#define UNICAM_DCM_DATA 1
> +#define UNICAM_SLS BIT(6)
> +#define UNICAM_PFT_MASK GENMASK(11, 8)
> +#define UNICAM_OET_MASK GENMASK(20, 12)
> +
> +/* UNICAM_STA Register */
> +#define UNICAM_SYN BIT(0)
> +#define UNICAM_CS BIT(1)
> +#define UNICAM_SBE BIT(2)
> +#define UNICAM_PBE BIT(3)
> +#define UNICAM_HOE BIT(4)
> +#define UNICAM_PLE BIT(5)
> +#define UNICAM_SSC BIT(6)
> +#define UNICAM_CRCE BIT(7)
> +#define UNICAM_OES BIT(8)
> +#define UNICAM_IFO BIT(9)
> +#define UNICAM_OFO BIT(10)
> +#define UNICAM_BFO BIT(11)
> +#define UNICAM_DL BIT(12)
> +#define UNICAM_PS BIT(13)
> +#define UNICAM_IS BIT(14)
> +#define UNICAM_PI0 BIT(15)
> +#define UNICAM_PI1 BIT(16)
> +#define UNICAM_FSI_S BIT(17)
> +#define UNICAM_FEI_S BIT(18)
> +#define UNICAM_LCI_S BIT(19)
> +#define UNICAM_BUF0_RDY BIT(20)
> +#define UNICAM_BUF0_NO BIT(21)
> +#define UNICAM_BUF1_RDY BIT(22)
> +#define UNICAM_BUF1_NO BIT(23)
> +#define UNICAM_DI BIT(24)
> +
> +#define UNICAM_STA_MASK_ALL \
> + (UNICAM_DL | \
> + UNICAM_SBE | \
> + UNICAM_PBE | \
> + UNICAM_HOE | \
> + UNICAM_PLE | \
> + UNICAM_SSC | \
> + UNICAM_CRCE | \
> + UNICAM_IFO | \
> + UNICAM_OFO | \
> + UNICAM_PS | \
> + UNICAM_PI0 | \
> + UNICAM_PI1)
> +
> +/* UNICAM_ANA Register */
> +#define UNICAM_APD BIT(0)
> +#define UNICAM_BPD BIT(1)
> +#define UNICAM_AR BIT(2)
> +#define UNICAM_DDL BIT(3)
> +#define UNICAM_CTATADJ_MASK GENMASK(7, 4)
> +#define UNICAM_PTATADJ_MASK GENMASK(11, 8)
> +
> +/* UNICAM_PRI Register */
> +#define UNICAM_PE BIT(0)
> +#define UNICAM_PT_MASK GENMASK(2, 1)
> +#define UNICAM_NP_MASK GENMASK(7, 4)
> +#define UNICAM_PP_MASK GENMASK(11, 8)
> +#define UNICAM_BS_MASK GENMASK(15, 12)
> +#define UNICAM_BL_MASK GENMASK(17, 16)
> +
> +/* UNICAM_CLK Register */
> +#define UNICAM_CLE BIT(0)
> +#define UNICAM_CLPD BIT(1)
> +#define UNICAM_CLLPE BIT(2)
> +#define UNICAM_CLHSE BIT(3)
> +#define UNICAM_CLTRE BIT(4)
> +#define UNICAM_CLAC_MASK GENMASK(8, 5)
> +#define UNICAM_CLSTE BIT(29)
> +
> +/* UNICAM_CLT Register */
> +#define UNICAM_CLT1_MASK GENMASK(7, 0)
> +#define UNICAM_CLT2_MASK GENMASK(15, 8)
> +
> +/* UNICAM_DATn Registers */
> +#define UNICAM_DLE BIT(0)
> +#define UNICAM_DLPD BIT(1)
> +#define UNICAM_DLLPE BIT(2)
> +#define UNICAM_DLHSE BIT(3)
> +#define UNICAM_DLTRE BIT(4)
> +#define UNICAM_DLSM BIT(5)
> +#define UNICAM_DLFO BIT(28)
> +#define UNICAM_DLSTE BIT(29)
> +
> +#define UNICAM_DAT_MASK_ALL (UNICAM_DLSTE | UNICAM_DLFO)
> +
> +/* UNICAM_DLT Register */
> +#define UNICAM_DLT1_MASK GENMASK(7, 0)
> +#define UNICAM_DLT2_MASK GENMASK(15, 8)
> +#define UNICAM_DLT3_MASK GENMASK(23, 16)
> +
> +/* UNICAM_ICTL Register */
> +#define UNICAM_FSIE BIT(0)
> +#define UNICAM_FEIE BIT(1)
> +#define UNICAM_IBOB BIT(2)
> +#define UNICAM_FCM BIT(3)
> +#define UNICAM_TFC BIT(4)
> +#define UNICAM_LIP_MASK GENMASK(6, 5)
> +#define UNICAM_LCIE_MASK GENMASK(28, 16)
> +
> +/* UNICAM_IDI0/1 Register */
> +#define UNICAM_ID0_MASK GENMASK(7, 0)
> +#define UNICAM_ID1_MASK GENMASK(15, 8)
> +#define UNICAM_ID2_MASK GENMASK(23, 16)
> +#define UNICAM_ID3_MASK GENMASK(31, 24)
> +
> +/* UNICAM_ISTA Register */
> +#define UNICAM_FSI BIT(0)
> +#define UNICAM_FEI BIT(1)
> +#define UNICAM_LCI BIT(2)
> +
> +#define UNICAM_ISTA_MASK_ALL (UNICAM_FSI | UNICAM_FEI | UNICAM_LCI)
> +
> +/* UNICAM_IPIPE Register */
> +#define UNICAM_PUM_MASK GENMASK(2, 0)
> +/* Unpacking modes */
> +#define UNICAM_PUM_NONE 0
> +#define UNICAM_PUM_UNPACK6 1
> +#define UNICAM_PUM_UNPACK7 2
> +#define UNICAM_PUM_UNPACK8 3
> +#define UNICAM_PUM_UNPACK10 4
> +#define UNICAM_PUM_UNPACK12 5
> +#define UNICAM_PUM_UNPACK14 6
> +#define UNICAM_PUM_UNPACK16 7
> +#define UNICAM_DDM_MASK GENMASK(6, 3)
> +#define UNICAM_PPM_MASK GENMASK(9, 7)
> +/* Packing modes */
> +#define UNICAM_PPM_NONE 0
> +#define UNICAM_PPM_PACK8 1
> +#define UNICAM_PPM_PACK10 2
> +#define UNICAM_PPM_PACK12 3
> +#define UNICAM_PPM_PACK14 4
> +#define UNICAM_PPM_PACK16 5
> +#define UNICAM_DEM_MASK GENMASK(11, 10)
> +#define UNICAM_DEBL_MASK GENMASK(14, 12)
> +#define UNICAM_ICM_MASK GENMASK(16, 15)
> +#define UNICAM_IDM_MASK GENMASK(17, 17)
> +
> +/* UNICAM_ICC Register */
> +#define UNICAM_ICFL_MASK GENMASK(4, 0)
> +#define UNICAM_ICFH_MASK GENMASK(9, 5)
> +#define UNICAM_ICST_MASK GENMASK(12, 10)
> +#define UNICAM_ICLT_MASK GENMASK(15, 13)
> +#define UNICAM_ICLL_MASK GENMASK(31, 16)
> +
> +/* UNICAM_DCS Register */
> +#define UNICAM_DIE BIT(0)
> +#define UNICAM_DIM BIT(1)
> +#define UNICAM_DBOB BIT(3)
> +#define UNICAM_FDE BIT(4)
> +#define UNICAM_LDP BIT(5)
> +#define UNICAM_EDL_MASK GENMASK(15, 8)
> +
> +/* UNICAM_DBCTL Register */
> +#define UNICAM_DBEN BIT(0)
> +#define UNICAM_BUF0_IE BIT(1)
> +#define UNICAM_BUF1_IE BIT(2)
> +
> +/* UNICAM_CMP[0,1] register */
> +#define UNICAM_PCE BIT(31)
> +#define UNICAM_GI BIT(9)
> +#define UNICAM_CPH BIT(8)
> +#define UNICAM_PCVC_MASK GENMASK(7, 6)
> +#define UNICAM_PCDT_MASK GENMASK(5, 0)
> +
> +/* UNICAM_MISC register */
> +#define UNICAM_FL0 BIT(6)
> +#define UNICAM_FL1 BIT(9)
> +
> +#endif
> diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam.c b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> new file mode 100644
> index 000000000000..470e691637c7
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> @@ -0,0 +1,2570 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * BCM283x / BCM271x Unicam Capture Driver
> + *
> + * Copyright (C) 2017-2020 - Raspberry Pi (Trading) Ltd.
> + *
> + * Dave Stevenson <[email protected]>
> + *
> + * Based on TI am437x driver by
> + * Benoit Parrot <[email protected]>
> + * Lad, Prabhakar <[email protected]>
> + *
> + * and TI CAL camera interface driver by
> + * Benoit Parrot <[email protected]>
> + *
> + *
> + * There are two camera drivers in the kernel for BCM283x - this one
> + * and bcm2835-camera (currently in staging).
> + *
> + * This driver directly controls the Unicam peripheral - there is no
> + * involvement with the VideoCore firmware. Unicam receives CSI-2 or
> + * CCP2 data and writes it into SDRAM.
> + * The only potential processing options are to repack Bayer data into an
> + * alternate format, and applying windowing.
> + * The repacking does not shift the data, so can repack V4L2_PIX_FMT_Sxxxx10P
> + * to V4L2_PIX_FMT_Sxxxx10, or V4L2_PIX_FMT_Sxxxx12P to V4L2_PIX_FMT_Sxxxx12,
> + * but not generically up to V4L2_PIX_FMT_Sxxxx16. The driver will add both
> + * formats where the relevant formats are defined, and will automatically
> + * configure the repacking as required.
> + * Support for windowing may be added later.
> + *
> + * It should be possible to connect this driver to any sensor with a
> + * suitable output interface and V4L2 subdevice driver.
> + *
> + * bcm2835-camera uses the VideoCore firmware to control the sensor,
> + * Unicam, ISP, and all tuner control loops. Fully processed frames are
> + * delivered to the driver by the firmware. It only has sensor drivers
> + * for Omnivision OV5647, and Sony IMX219 sensors.
> + *
> + * The two drivers are mutually exclusive for the same Unicam instance.
> + * The VideoCore firmware checks the device tree configuration during boot.
> + * If it finds device tree nodes called csi0 or csi1 it will block the
> + * firmware from accessing the peripheral, and bcm2835-camera will
> + * not be able to stream data.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/of_graph.h>
> +#include <linux/pinctrl/consumer.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +#include <linux/videodev2.h>
> +
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-dv-timings.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "bcm2835-unicam-regs.h"
> +
> +#define UNICAM_MODULE_NAME "unicam"
> +
> +/*
> + * Unicam must request a minimum of 250Mhz from the VPU clock.
> + * Otherwise the input FIFOs overrun and cause image corruption.
> + */
> +#define UNICAM_MIN_VPU_CLOCK_RATE (250 * 1000 * 1000)
> +
> +/*
> + * Stride is a 16 bit register, but also has to be a multiple of 32.
> + */
> +#define UNICAM_BPL_ALIGNMENT 32
> +#define UNICAM_MAX_BYTESPERLINE ((1 << 16) - UNICAM_BPL_ALIGNMENT)
> +/*
> + * Max width is therefore determined by the max stride divided by
> + * the number of bits per pixel. Take 32bpp as a
> + * worst case.
> + * No imposed limit on the height, so adopt a square image for want
> + * of anything better.
> + */
> +#define UNICAM_MAX_WIDTH (UNICAM_MAX_BYTESPERLINE / 4)
> +#define UNICAM_MAX_HEIGHT UNICAM_MAX_WIDTH
> +/* Define a nominal minimum image size */
> +#define UNICAM_MIN_WIDTH 16
> +#define UNICAM_MIN_HEIGHT 16
> +/* Default size of the embedded buffer */
> +#define UNICAM_EMBEDDED_SIZE 16384
> +
> +/*
> + * Size of the dummy buffer. Can be any size really, but the DMA
> + * allocation works in units of page sizes.
> + */
> +#define UNICAM_DUMMY_BUF_SIZE PAGE_SIZE
> +
> +#define UNICAM_SD_PAD_SINK 0
> +#define UNICAM_SD_PAD_SOURCE_IMAGE 1
> +#define UNICAM_SD_PAD_SOURCE_METADATA 2
> +#define UNICAM_SD_NUM_PADS (1 + UNICAM_SD_PAD_SOURCE_METADATA)
> +
> +static inline bool unicam_sd_pad_is_sink(u32 pad)
> +{
> + /* Camera RX has 1 sink pad, and N source pads */
> + return pad == 0;
> +}
s/0/UNICAM_SD_PAD_SINK ?
> +
> +static inline bool unicam_sd_pad_is_source(u32 pad)
> +{
> + /* Camera RX has 1 sink pad, and N source pads */
> + return pad != UNICAM_SD_PAD_SINK;
> +}
> +
> +enum unicam_node_type {
> + UNICAM_IMAGE_NODE,
> + UNICAM_METADATA_NODE,
> + UNICAM_MAX_NODES
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Structure definitions
> + */
please drop
> +
> +/*
> + * struct unicam_fmt - Unicam media bus format information
> + * @fourcc: V4L2 pixel format FCC identifier. 0 if n/a.
> + * @unpacked_fourcc: V4L2 pixel format FCC identifier if the data is expanded
> + * out to 16bpp. 0 if n/a.
> + * @code: V4L2 media bus format code.
> + * @depth: Bits per pixel as delivered from the source.
> + * @csi_dt: CSI data type.
> + * @metadata_fmt: This format only applies to the metadata pad.
> + */
> +struct unicam_fmt {
> + u32 fourcc;
> + u32 unpacked_fourcc;
> + u32 code;
> + u8 depth;
> + u8 csi_dt;
> + u8 metadata_fmt:1;
> +};
> +
> +static const struct unicam_fmt unicam_image_formats[] = {
> + /* YUV Formats */
> + {
> + .fourcc = V4L2_PIX_FMT_YUYV,
> + .code = MEDIA_BUS_FMT_YUYV8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + .fourcc = V4L2_PIX_FMT_UYVY,
> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + .fourcc = V4L2_PIX_FMT_YVYU,
> + .code = MEDIA_BUS_FMT_YVYU8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + .fourcc = V4L2_PIX_FMT_VYUY,
> + .code = MEDIA_BUS_FMT_VYUY8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + /* RGB Formats */
> + .fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */
> + .code = MEDIA_BUS_FMT_RGB565_1X16,
> + .depth = 16,
> + .csi_dt = 0x22,
> + }, {
> + .fourcc = V4L2_PIX_FMT_RGB24, /* rgb */
> + .code = MEDIA_BUS_FMT_RGB888_1X24,
> + .depth = 24,
> + .csi_dt = 0x24,
> + }, {
> + .fourcc = V4L2_PIX_FMT_BGR24, /* bgr */
> + .code = MEDIA_BUS_FMT_BGR888_1X24,
> + .depth = 24,
> + .csi_dt = 0x24,
> + }, {
> + /* Bayer Formats */
> + .fourcc = V4L2_PIX_FMT_SBGGR8,
> + .code = MEDIA_BUS_FMT_SBGGR8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG8,
> + .code = MEDIA_BUS_FMT_SGBRG8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG8,
> + .code = MEDIA_BUS_FMT_SGRBG8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB8,
> + .code = MEDIA_BUS_FMT_SRGGB8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SBGGR10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SBGGR10,
> + .code = MEDIA_BUS_FMT_SBGGR10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGBRG10,
> + .code = MEDIA_BUS_FMT_SGBRG10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGRBG10,
> + .code = MEDIA_BUS_FMT_SGRBG10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SRGGB10,
> + .code = MEDIA_BUS_FMT_SRGGB10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SBGGR12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SBGGR12,
> + .code = MEDIA_BUS_FMT_SBGGR12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGBRG12,
> + .code = MEDIA_BUS_FMT_SGBRG12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGRBG12,
> + .code = MEDIA_BUS_FMT_SGRBG12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SRGGB12,
> + .code = MEDIA_BUS_FMT_SRGGB12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SBGGR14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SBGGR14,
> + .code = MEDIA_BUS_FMT_SBGGR14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGBRG14,
> + .code = MEDIA_BUS_FMT_SGBRG14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGRBG14,
> + .code = MEDIA_BUS_FMT_SGRBG14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SRGGB14,
> + .code = MEDIA_BUS_FMT_SRGGB14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + /* 16 bit Bayer formats could be supported. */
> +
> + /* Greyscale formats */
> + .fourcc = V4L2_PIX_FMT_GREY,
> + .code = MEDIA_BUS_FMT_Y8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_Y10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_Y10,
> + .code = MEDIA_BUS_FMT_Y10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_Y12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_Y12,
> + .code = MEDIA_BUS_FMT_Y12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_Y14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_Y14,
> + .code = MEDIA_BUS_FMT_Y14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + },
> +};
> +
> +static const struct unicam_fmt unicam_meta_formats[] = {
> + {
> + .fourcc = V4L2_META_FMT_8,
> + .code = MEDIA_BUS_FMT_METADATA_8,
> + .depth = 8,
> + .metadata_fmt = 1,
> + }
> +};
> +
> +static const struct v4l2_mbus_framefmt default_image_format = {
> + .width = 640,
> + .height = 480,
> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_SRGB,
> + .ycbcr_enc = V4L2_YCBCR_ENC_601,
> + .quantization = V4L2_QUANTIZATION_LIM_RANGE,
> + .xfer_func = V4L2_XFER_FUNC_SRGB,
> + .flags = 0,
> +};
> +
> +struct unicam_buffer {
> + struct vb2_v4l2_buffer vb;
> + struct list_head list;
> +};
> +
> +static inline struct unicam_buffer *to_unicam_buffer(struct vb2_buffer *vb)
> +{
> + return container_of(vb, struct unicam_buffer, vb.vb2_buf);
> +}
> +
> +struct unicam_node {
> + bool registered;
> + bool streaming;
> + unsigned int id;
> +
> + /* Pointer to the current v4l2_buffer */
> + struct unicam_buffer *cur_frm;
> + /* Pointer to the next v4l2_buffer */
> + struct unicam_buffer *next_frm;
> + /* video capture */
> + const struct unicam_fmt *fmt;
> + /* Used to store current pixel format */
> + struct v4l2_format v_fmt;
> + /* Buffer queue used in video-buf */
> + struct vb2_queue buffer_queue;
> + /* Queue of filled frames */
> + struct list_head dma_queue;
> + /* IRQ lock for DMA queue */
> + spinlock_t dma_queue_lock;
> + /* lock used to access this structure */
> + struct mutex lock;
> + /* Identifies video device for this channel */
> + struct video_device video_dev;
> + /* Pointer to the parent handle */
> + struct unicam_device *dev;
> + struct media_pad pad;
> + /*
> + * Dummy buffer intended to be used by unicam
> + * if we have no other queued buffers to swap to.
> + */
> + void *dummy_buf_cpu_addr;
> + dma_addr_t dummy_buf_dma_addr;
> +};
> +
> +struct unicam_device {
> + struct kref kref;
> +
> + /* peripheral base address */
> + void __iomem *base;
> + /* clock gating base address */
> + void __iomem *clk_gate_base;
> + /* lp clock handle */
> + struct clk *clock;
> + /* vpu clock handle */
> + struct clk *vpu_clock;
> + /* V4l2 device */
> + struct v4l2_device v4l2_dev;
> + struct media_device mdev;
> +
> + /* parent device */
> + struct device *dev;
> + /* subdevice async Notifier */
> + struct v4l2_async_notifier notifier;
> + unsigned int sequence;
> +
> + /* Sensor node */
> + struct {
> + struct v4l2_subdev *subdev;
> + struct media_pad *pad;
> + } sensor;
> +
> + /* Internal subdev */
> + struct {
> + struct v4l2_subdev sd;
> + struct media_pad pads[UNICAM_SD_NUM_PADS];
> + bool streaming;
> + } subdev;
> +
> + enum v4l2_mbus_type bus_type;
> + /*
> + * Stores bus.mipi_csi2.flags for CSI2 sensors, or
> + * bus.mipi_csi1.strobe for CCP2.
> + */
> + unsigned int bus_flags;
> + unsigned int max_data_lanes;
> + unsigned int active_data_lanes;
> +
> + struct media_pipeline pipe;
> +
> + struct unicam_node node[UNICAM_MAX_NODES];
> +};
> +
> +static inline struct unicam_device *
> +notifier_to_unicam_device(struct v4l2_async_notifier *notifier)
> +{
> + return container_of(notifier, struct unicam_device, notifier);
> +}
> +
> +static inline struct unicam_device *
> +sd_to_unicam_device(struct v4l2_subdev *sd)
> +{
> + return container_of(sd, struct unicam_device, subdev.sd);
> +}
> +
> +static void unicam_release(struct kref *kref)
> +{
> + struct unicam_device *unicam =
> + container_of(kref, struct unicam_device, kref);
> +
> + if (unicam->mdev.dev)
> + media_device_cleanup(&unicam->mdev);
> +
> + kfree(unicam);
> +}
> +
> +static void unicam_put(struct unicam_device *unicam)
> +{
> + kref_put(&unicam->kref, unicam_release);
> +}
> +
> +static struct unicam_device *unicam_get(struct unicam_device *unicam)
> +{
> + kref_get(&unicam->kref);
> + return unicam;
> +}
> +
> +static void unicam_node_release(struct video_device *vdev)
> +{
> + struct unicam_node *node = video_get_drvdata(vdev);
> +
> + unicam_put(node->dev);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Misc helper functions
> + */
please drop
> +
> +static inline bool is_metadata_node(struct unicam_node *node)
> +{
> + return node->video_dev.device_caps & V4L2_CAP_META_CAPTURE;
> +}
> +
> +static inline bool is_image_node(struct unicam_node *node)
> +{
> + return node->video_dev.device_caps & V4L2_CAP_VIDEO_CAPTURE;
> +}
> +
> +/* Hardware access */
> +static inline void unicam_clk_write(struct unicam_device *unicam, u32 val)
> +{
> + /* Pass the CM_PASSWORD along with the value. */
> + writel(val | 0x5a000000, unicam->clk_gate_base);
> +}
> +
> +static inline u32 unicam_reg_read(struct unicam_device *unicam, u32 offset)
> +{
> + return readl(unicam->base + offset);
> +}
> +
> +static inline void unicam_reg_write(struct unicam_device *unicam, u32 offset, u32 val)
> +{
> + writel(val, unicam->base + offset);
> +}
> +
> +static inline int unicam_get_field(u32 value, u32 mask)
> +{
> + return (value & mask) >> __ffs(mask);
> +}
> +
> +static inline void unicam_set_field(u32 *valp, u32 field, u32 mask)
> +{
> + u32 val = *valp;
> +
> + val &= ~mask;
> + val |= (field << __ffs(mask)) & mask;
> + *valp = val;
> +}
> +
> +static inline void unicam_reg_write_field(struct unicam_device *unicam, u32 offset,
> + u32 field, u32 mask)
> +{
> + u32 val = unicam_reg_read(unicam, offset);
> +
> + unicam_set_field(&val, field, mask);
> + unicam_reg_write(unicam, offset, val);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Format data table and helper functions
> + */
please drop
> +
> +/* Format setup functions */
> +static const struct unicam_fmt *unicam_find_format_by_code(u32 code, u32 pad)
> +{
> + const struct unicam_fmt *formats;
> + unsigned int num_formats;
> + unsigned int i;
> +
> + if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + formats = unicam_image_formats;
> + num_formats = ARRAY_SIZE(unicam_image_formats);
> + } else {
> + formats = unicam_meta_formats;
> + num_formats = ARRAY_SIZE(unicam_meta_formats);
> + }
> +
> + for (i = 0; i < num_formats; i++) {
> + if (formats[i].code == code)
> + return &formats[i];
> + }
> +
> + return NULL;
> +}
> +
> +static const struct unicam_fmt *unicam_find_format_by_fourcc(u32 fourcc, u32 pad)
> +{
> + const struct unicam_fmt *formats;
> + unsigned int num_formats;
> + unsigned int i;
> +
> + if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + formats = unicam_image_formats;
> + num_formats = ARRAY_SIZE(unicam_image_formats);
> + } else {
> + formats = unicam_meta_formats;
> + num_formats = ARRAY_SIZE(unicam_meta_formats);
> + }
> +
> + for (i = 0; i < num_formats; ++i) {
> + if (formats[i].fourcc == fourcc)
> + return &formats[i];
> + }
> +
> + return NULL;
> +}
> +
> +static int unicam_calc_format_size_bpl(struct unicam_device *unicam,
> + const struct unicam_fmt *fmt,
> + struct v4l2_format *f)
> +{
> + u32 min_bpl;
> +
> + v4l_bound_align_image(&f->fmt.pix.width, UNICAM_MIN_WIDTH,
> + UNICAM_MAX_WIDTH, 2,
> + &f->fmt.pix.height, UNICAM_MIN_HEIGHT,
> + UNICAM_MAX_HEIGHT, 0, 0);
> +
> + /* Unpacking always goes to 16bpp */
> + if (f->fmt.pix.pixelformat == fmt->unpacked_fourcc)
> + min_bpl = f->fmt.pix.width * 2;
> + else
> + min_bpl = f->fmt.pix.width * fmt->depth / 8;
> + min_bpl = ALIGN(min_bpl, UNICAM_BPL_ALIGNMENT);
> +
> + f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline,
> + UNICAM_BPL_ALIGNMENT);
> + f->fmt.pix.bytesperline = clamp_t(unsigned int,
> + f->fmt.pix.bytesperline,
> + min_bpl,
> + UNICAM_MAX_BYTESPERLINE);
> +
> + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
> +
> + dev_dbg(unicam->dev, "%s: fourcc: %08X size: %ux%u bpl:%u img_size:%u\n",
> + __func__,
> + f->fmt.pix.pixelformat,
> + f->fmt.pix.width, f->fmt.pix.height,
> + f->fmt.pix.bytesperline, f->fmt.pix.sizeimage);
> +
> + return 0;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Hardware handling
> + */
please drop
> +
> +static void unicam_wr_dma_addr(struct unicam_node *node, dma_addr_t dmaaddr,
> + unsigned int buffer_size)
> +{
> + dma_addr_t endaddr = dmaaddr + buffer_size;
> +
> + if (node->id == UNICAM_IMAGE_NODE) {
> + unicam_reg_write(node->dev, UNICAM_IBSA0, dmaaddr);
> + unicam_reg_write(node->dev, UNICAM_IBEA0, endaddr);
> + } else {
> + unicam_reg_write(node->dev, UNICAM_DBSA0, dmaaddr);
> + unicam_reg_write(node->dev, UNICAM_DBEA0, endaddr);
> + }
> +}
> +
> +static unsigned int unicam_get_lines_done(struct unicam_device *unicam)
> +{
> + dma_addr_t start_addr, cur_addr;
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> + unsigned int stride = node->v_fmt.fmt.pix.bytesperline;
> + struct unicam_buffer *frm = node->cur_frm;
> +
> + if (!frm)
> + return 0;
> +
> + start_addr = vb2_dma_contig_plane_dma_addr(&frm->vb.vb2_buf, 0);
> + cur_addr = unicam_reg_read(unicam, UNICAM_IBWP);
> + return (unsigned int)(cur_addr - start_addr) / stride;
> +}
> +
> +static void unicam_schedule_next_buffer(struct unicam_node *node)
> +{
> + struct unicam_buffer *buf;
> + unsigned int size;
> + dma_addr_t addr;
> +
> + buf = list_first_entry(&node->dma_queue, struct unicam_buffer, list);
> + node->next_frm = buf;
> + list_del(&buf->list);
> +
> + addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> + if (is_image_node(node))
> + size = node->v_fmt.fmt.pix.sizeimage;
> + else
> + size = node->v_fmt.fmt.meta.buffersize;
> +
> + unicam_wr_dma_addr(node, addr, size);
> +}
> +
> +static void unicam_schedule_dummy_buffer(struct unicam_node *node)
> +{
> + struct unicam_device *unicam = node->dev;
> + int node_id = is_image_node(node) ? UNICAM_IMAGE_NODE : UNICAM_METADATA_NODE;
> +
> + dev_dbg(unicam->dev, "Scheduling dummy buffer for node %d\n", node_id);
> +
> + unicam_wr_dma_addr(node, node->dummy_buf_dma_addr,
> + UNICAM_DUMMY_BUF_SIZE);
> +
> + node->next_frm = NULL;
> +}
> +
> +static void unicam_process_buffer_complete(struct unicam_node *node,
> + unsigned int sequence)
> +{
> + node->cur_frm->vb.field = node->v_fmt.fmt.pix.field;
> + node->cur_frm->vb.sequence = sequence;
> +
> + vb2_buffer_done(&node->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +}
> +
> +static void unicam_queue_event_sof(struct unicam_device *unicam)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> +
> + struct v4l2_event event = {
> + .type = V4L2_EVENT_FRAME_SYNC,
> + .u.frame_sync.frame_sequence = unicam->sequence,
> + };
> +
> + v4l2_event_queue(&node->video_dev, &event);
> +}
> +
> +/*
> + * unicam_isr : ISR handler for unicam capture
> + * @irq: irq number
> + * @dev_id: dev_id ptr
these kernel-doc doesn't match with the definition. dev seems to be the
unicam instance?
> + *
> + * It changes status of the captured buffer, takes next buffer from the queue
> + * and sets its address in unicam registers
> + */
> +static irqreturn_t unicam_isr(int irq, void *dev)
> +{
> + struct unicam_device *unicam = dev;
> + unsigned int lines_done = unicam_get_lines_done(dev);
> + unsigned int sequence = unicam->sequence;
> + unsigned int i;
> + u32 ista, sta;
> + bool fe;
> + u64 ts;
> +
> + sta = unicam_reg_read(unicam, UNICAM_STA);
> + /* Write value back to clear the interrupts */
> + unicam_reg_write(unicam, UNICAM_STA, sta);
> +
> + ista = unicam_reg_read(unicam, UNICAM_ISTA);
> + /* Write value back to clear the interrupts */
> + unicam_reg_write(unicam, UNICAM_ISTA, ista);
> +
> + dev_dbg(unicam->dev, "ISR: ISTA: 0x%X, STA: 0x%X, sequence %d, lines done %d",
> + ista, sta, sequence, lines_done);
> +
> + if (!(sta & (UNICAM_IS | UNICAM_PI0)))
> + return IRQ_HANDLED;
> +
> + /*
> + * Look for either the Frame End interrupt or the Packet Capture status
> + * to signal a frame end.
> + */
> + fe = ista & UNICAM_FEI || sta & UNICAM_PI0;
> +
> + /*
> + * We must run the frame end handler first. If we have a valid next_frm
> + * and we get a simultaneout FE + FS interrupt, running the FS handler
> + * first would null out the next_frm ptr and we would have lost the
> + * buffer forever.
> + */
> + if (fe) {
> + /*
> + * Ensure we have swapped buffers already as we can't
> + * stop the peripheral. If no buffer is available, use a
> + * dummy buffer to dump out frames until we get a new buffer
> + * to use.
> + */
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + if (!unicam->node[i].streaming)
> + continue;
> +
> + /*
> + * If cur_frm == next_frm, it means we have not had
> + * a chance to swap buffers, likely due to having
> + * multiple interrupts occurring simultaneously (like FE
> + * + FS + LS). In this case, we cannot signal the buffer
> + * as complete, as the HW will reuse that buffer.
> + */
> + if (unicam->node[i].cur_frm &&
> + unicam->node[i].cur_frm != unicam->node[i].next_frm)
> + unicam_process_buffer_complete(&unicam->node[i],
> + sequence);
> + unicam->node[i].cur_frm = unicam->node[i].next_frm;
> + }
> + unicam->sequence++;
> + }
> +
> + if (ista & UNICAM_FSI) {
> + /*
> + * Timestamp is to be when the first data byte was captured,
> + * aka frame start.
> + */
> + ts = ktime_get_ns();
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + if (!unicam->node[i].streaming)
> + continue;
> +
> + if (unicam->node[i].cur_frm)
> + unicam->node[i].cur_frm->vb.vb2_buf.timestamp =
> + ts;
> + else
> + dev_dbg(unicam->v4l2_dev.dev,
> + "ISR: [%d] Dropping frame, buffer not available at FS\n",
> + i);
> + /*
> + * Set the next frame output to go to a dummy frame
> + * if we have not managed to obtain another frame
> + * from the queue.
> + */
> + unicam_schedule_dummy_buffer(&unicam->node[i]);
> + }
> +
> + unicam_queue_event_sof(unicam);
> + }
> +
> + /*
> + * Cannot swap buffer at frame end, there may be a race condition
> + * where the HW does not actually swap it if the new frame has
> + * already started.
> + */
> + if (ista & (UNICAM_FSI | UNICAM_LCI) && !fe) {
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + if (!unicam->node[i].streaming)
> + continue;
> +
> + spin_lock(&unicam->node[i].dma_queue_lock);
> + if (!list_empty(&unicam->node[i].dma_queue) &&
> + !unicam->node[i].next_frm)
> + unicam_schedule_next_buffer(&unicam->node[i]);
> + spin_unlock(&unicam->node[i].dma_queue_lock);
> + }
> + }
> +
> + if (unicam_reg_read(unicam, UNICAM_ICTL) & UNICAM_FCM) {
> + /* Switch out of trigger mode if selected */
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_TFC);
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 0, UNICAM_FCM);
> + }
> + return IRQ_HANDLED;
> +}
> +
> +static void unicam_set_packing_config(struct unicam_device *unicam)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> + u32 pack, unpack;
> + u32 val;
> +
> + if (node->v_fmt.fmt.pix.pixelformat ==
> + node->fmt->fourcc) {
> + unpack = UNICAM_PUM_NONE;
> + pack = UNICAM_PPM_NONE;
> + } else {
> + switch (node->fmt->depth) {
> + case 8:
> + unpack = UNICAM_PUM_UNPACK8;
> + break;
> + case 10:
> + unpack = UNICAM_PUM_UNPACK10;
> + break;
> + case 12:
> + unpack = UNICAM_PUM_UNPACK12;
> + break;
> + case 14:
> + unpack = UNICAM_PUM_UNPACK14;
> + break;
> + case 16:
> + unpack = UNICAM_PUM_UNPACK16;
> + break;
> + default:
> + unpack = UNICAM_PUM_NONE;
> + break;
> + }
> +
> + /* Repacking is always to 16bpp */
> + pack = UNICAM_PPM_PACK16;
> + }
> +
> + val = 0;
> + unicam_set_field(&val, unpack, UNICAM_PUM_MASK);
> + unicam_set_field(&val, pack, UNICAM_PPM_MASK);
> + unicam_reg_write(unicam, UNICAM_IPIPE, val);
> +}
> +
> +static void unicam_cfg_image_id(struct unicam_device *unicam)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> +
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + /* CSI2 mode, hardcode VC 0 for now. */
> + unicam_reg_write(unicam, UNICAM_IDI0,
> + (0 << 6) | node->fmt->csi_dt);
> + } else {
> + /* CCP2 mode */
> + unicam_reg_write(unicam, UNICAM_IDI0,
> + 0x80 | node->fmt->csi_dt);
> + }
> +}
> +
> +static void unicam_enable_ed(struct unicam_device *unicam)
> +{
> + u32 val = unicam_reg_read(unicam, UNICAM_DCS);
> +
> + unicam_set_field(&val, 2, UNICAM_EDL_MASK);
> + /* Do not wrap at the end of the embedded data buffer */
> + unicam_set_field(&val, 0, UNICAM_DBOB);
> +
> + unicam_reg_write(unicam, UNICAM_DCS, val);
> +}
> +
> +static void unicam_start_rx(struct unicam_device *unicam, dma_addr_t *addr)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> + int line_int_freq = node->v_fmt.fmt.pix.height >> 2;
> + unsigned int size, i;
> + u32 val;
> +
> + if (line_int_freq < 128)
> + line_int_freq = 128;
> +
> + /* Enable lane clocks */
> + val = 1;
> + for (i = 0; i < unicam->active_data_lanes; i++)
> + val = val << 2 | 1;
> + unicam_clk_write(unicam, val);
> +
> + /* Basic init */
> + unicam_reg_write(unicam, UNICAM_CTRL, UNICAM_MEM);
> +
> + /* Enable analogue control, and leave in reset. */
> + val = UNICAM_AR;
> + unicam_set_field(&val, 7, UNICAM_CTATADJ_MASK);
> + unicam_set_field(&val, 7, UNICAM_PTATADJ_MASK);
> + unicam_reg_write(unicam, UNICAM_ANA, val);
> + usleep_range(1000, 2000);
> +
> + /* Come out of reset */
> + unicam_reg_write_field(unicam, UNICAM_ANA, 0, UNICAM_AR);
> +
> + /* Peripheral reset */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPR);
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPR);
> +
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPE);
> +
> + /* Enable Rx control. */
> + val = unicam_reg_read(unicam, UNICAM_CTRL);
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + unicam_set_field(&val, UNICAM_CPM_CSI2, UNICAM_CPM_MASK);
> + unicam_set_field(&val, UNICAM_DCM_STROBE, UNICAM_DCM_MASK);
> + } else {
> + unicam_set_field(&val, UNICAM_CPM_CCP2, UNICAM_CPM_MASK);
> + unicam_set_field(&val, unicam->bus_flags, UNICAM_DCM_MASK);
> + }
> + /* Packet framer timeout */
> + unicam_set_field(&val, 0xf, UNICAM_PFT_MASK);
> + unicam_set_field(&val, 128, UNICAM_OET_MASK);
> + unicam_reg_write(unicam, UNICAM_CTRL, val);
> +
> + unicam_reg_write(unicam, UNICAM_IHWIN, 0);
> + unicam_reg_write(unicam, UNICAM_IVWIN, 0);
> +
> + /* AXI bus access QoS setup */
> + val = unicam_reg_read(unicam, UNICAM_PRI);
> + unicam_set_field(&val, 0, UNICAM_BL_MASK);
> + unicam_set_field(&val, 0, UNICAM_BS_MASK);
> + unicam_set_field(&val, 0xe, UNICAM_PP_MASK);
> + unicam_set_field(&val, 8, UNICAM_NP_MASK);
> + unicam_set_field(&val, 2, UNICAM_PT_MASK);
> + unicam_set_field(&val, 1, UNICAM_PE);
> + unicam_reg_write(unicam, UNICAM_PRI, val);
> +
> + unicam_reg_write_field(unicam, UNICAM_ANA, 0, UNICAM_DDL);
> +
> + /* Always start in trigger frame capture mode (UNICAM_FCM set) */
> + val = UNICAM_FSIE | UNICAM_FEIE | UNICAM_FCM | UNICAM_IBOB;
> + unicam_set_field(&val, line_int_freq, UNICAM_LCIE_MASK);
> + unicam_reg_write(unicam, UNICAM_ICTL, val);
> + unicam_reg_write(unicam, UNICAM_STA, UNICAM_STA_MASK_ALL);
> + unicam_reg_write(unicam, UNICAM_ISTA, UNICAM_ISTA_MASK_ALL);
> +
> + /* tclk_term_en */
> + unicam_reg_write_field(unicam, UNICAM_CLT, 2, UNICAM_CLT1_MASK);
> + /* tclk_settle */
> + unicam_reg_write_field(unicam, UNICAM_CLT, 6, UNICAM_CLT2_MASK);
> + /* td_term_en */
> + unicam_reg_write_field(unicam, UNICAM_DLT, 2, UNICAM_DLT1_MASK);
> + /* ths_settle */
> + unicam_reg_write_field(unicam, UNICAM_DLT, 6, UNICAM_DLT2_MASK);
> + /* trx_enable */
> + unicam_reg_write_field(unicam, UNICAM_DLT, 0, UNICAM_DLT3_MASK);
> +
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_SOE);
> +
> + /* Packet compare setup - required to avoid missing frame ends */
> + val = 0;
> + unicam_set_field(&val, 1, UNICAM_PCE);
> + unicam_set_field(&val, 1, UNICAM_GI);
> + unicam_set_field(&val, 1, UNICAM_CPH);
> + unicam_set_field(&val, 0, UNICAM_PCVC_MASK);
> + unicam_set_field(&val, 1, UNICAM_PCDT_MASK);
> + unicam_reg_write(unicam, UNICAM_CMP0, val);
> +
> + /* Enable clock lane and set up terminations */
> + val = 0;
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + /* CSI2 */
> + unicam_set_field(&val, 1, UNICAM_CLE);
> + unicam_set_field(&val, 1, UNICAM_CLLPE);
> + if (unicam->bus_flags & V4L2_MBUS_CSI2_CONTINUOUS_CLOCK) {
> + unicam_set_field(&val, 1, UNICAM_CLTRE);
> + unicam_set_field(&val, 1, UNICAM_CLHSE);
> + }
> + } else {
> + /* CCP2 */
> + unicam_set_field(&val, 1, UNICAM_CLE);
> + unicam_set_field(&val, 1, UNICAM_CLHSE);
> + unicam_set_field(&val, 1, UNICAM_CLTRE);
> + }
> + unicam_reg_write(unicam, UNICAM_CLK, val);
> +
> + /*
> + * Enable required data lanes with appropriate terminations.
> + * The same value needs to be written to UNICAM_DATn registers for
> + * the active lanes, and 0 for inactive ones.
> + */
> + val = 0;
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + /* CSI2 */
> + unicam_set_field(&val, 1, UNICAM_DLE);
> + unicam_set_field(&val, 1, UNICAM_DLLPE);
> + if (unicam->bus_flags & V4L2_MBUS_CSI2_CONTINUOUS_CLOCK) {
> + unicam_set_field(&val, 1, UNICAM_DLTRE);
> + unicam_set_field(&val, 1, UNICAM_DLHSE);
> + }
> + } else {
> + /* CCP2 */
> + unicam_set_field(&val, 1, UNICAM_DLE);
> + unicam_set_field(&val, 1, UNICAM_DLHSE);
> + unicam_set_field(&val, 1, UNICAM_DLTRE);
> + }
> + unicam_reg_write(unicam, UNICAM_DAT0, val);
> +
> + if (unicam->active_data_lanes == 1)
> + val = 0;
> + unicam_reg_write(unicam, UNICAM_DAT1, val);
> +
> + if (unicam->max_data_lanes > 2) {
> + /*
> + * Registers UNICAM_DAT2 and UNICAM_DAT3 only valid if the
> + * instance supports more than 2 data lanes.
> + */
> + if (unicam->active_data_lanes == 2)
> + val = 0;
> + unicam_reg_write(unicam, UNICAM_DAT2, val);
> +
> + if (unicam->active_data_lanes == 3)
> + val = 0;
> + unicam_reg_write(unicam, UNICAM_DAT3, val);
> + }
> +
> + unicam_reg_write(unicam, UNICAM_IBLS,
> + node->v_fmt.fmt.pix.bytesperline);
> + size = node->v_fmt.fmt.pix.sizeimage;
> + unicam_wr_dma_addr(&unicam->node[UNICAM_IMAGE_NODE],
> + addr[UNICAM_IMAGE_NODE], size);
> + unicam_set_packing_config(unicam);
> + unicam_cfg_image_id(unicam);
> +
> + val = unicam_reg_read(unicam, UNICAM_MISC);
> + unicam_set_field(&val, 1, UNICAM_FL0);
> + unicam_set_field(&val, 1, UNICAM_FL1);
> + unicam_reg_write(unicam, UNICAM_MISC, val);
> +
> + /* Enable peripheral */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPE);
> +
> + /* Load image pointers */
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_LIP_MASK);
> +
> + /*
> + * Enable trigger only for the first frame to
> + * sync correctly to the FS from the source.
> + */
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_TFC);
> +}
> +
> +static void unicam_start_metadata(struct unicam_device *unicam, dma_addr_t *addr)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_METADATA_NODE];
> + unsigned int size;
> +
> + dev_dbg(unicam->dev, "enable metadata dma\n");
> + size = node->v_fmt.fmt.meta.buffersize;
> + unicam_enable_ed(unicam);
> + unicam_wr_dma_addr(node, addr[UNICAM_METADATA_NODE], size);
> + unicam_reg_write_field(unicam, UNICAM_DCS, 1, UNICAM_LDP);
> +}
> +
> +static void unicam_disable(struct unicam_device *unicam)
> +{
> + /* Analogue lane control disable */
> + unicam_reg_write_field(unicam, UNICAM_ANA, 1, UNICAM_DDL);
> +
> + /* Stop the output engine */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_SOE);
> +
> + /* Disable the data lanes. */
> + unicam_reg_write(unicam, UNICAM_DAT0, 0);
> + unicam_reg_write(unicam, UNICAM_DAT1, 0);
> +
> + if (unicam->max_data_lanes > 2) {
> + unicam_reg_write(unicam, UNICAM_DAT2, 0);
> + unicam_reg_write(unicam, UNICAM_DAT3, 0);
> + }
> +
> + /* Peripheral reset */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPR);
> + usleep_range(50, 100);
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPR);
> +
> + /* Disable peripheral */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPE);
> +
> + /* Clear ED setup */
> + unicam_reg_write(unicam, UNICAM_DCS, 0);
> +
> + /* Disable all lane clocks */
> + unicam_clk_write(unicam, 0);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 subdev Operations
> + */
please drop
> +
> +static int __unicam_subdev_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_krouting *routing)
> +{
> + int ret;
> +
> + ret = v4l2_subdev_routing_validate_1_to_1(routing);
> + if (ret)
> + return ret;
> +
> + v4l2_subdev_lock_state(state);
> +
> + ret = v4l2_subdev_set_routing(sd, state, routing);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_subdev_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + enum v4l2_subdev_format_whence which,
> + struct v4l2_subdev_krouting *routing)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> +
> + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && unicam->subdev.streaming)
> + return -EBUSY;
> +
> + return __unicam_subdev_set_routing(sd, state, routing);
> +}
> +
> +static int unicam_subdev_init_cfg(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state)
> +{
> + struct v4l2_subdev_route routes[] = {
> + {
> + .sink_pad = UNICAM_SD_PAD_SINK,
> + .sink_stream = 0,
> + .source_pad = UNICAM_SD_PAD_SOURCE_IMAGE,
> + .source_stream = 0,
> + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> + },
> + };
> +
> + struct v4l2_subdev_krouting routing = {
> + .num_routes = ARRAY_SIZE(routes),
> + .routes = routes,
> + };
> +
> + /* Initialize routing to single route to the fist source pad */
> + return __unicam_subdev_set_routing(sd, state, &routing);
> +}
> +
> +static int unicam_subdev_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + u32 pad, stream;
> + int ret = -EINVAL;
> +
> + v4l2_subdev_lock_state(state);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, code->pad,
> + code->stream, &pad,
> + &stream);
> + if (ret)
> + goto out;
> +
> + if (unicam_sd_pad_is_source(code->pad)) {
> + /* No transcoding, source and sink codes must match. */
> + struct v4l2_mbus_framefmt *fmt;
> +
> + fmt = v4l2_state_get_stream_format(state, pad, stream);
> + if (!fmt)
> + goto out;
> +
> + if (code->index > 0)
> + goto out;
> +
> + code->code = fmt->code;
> + } else {
> + const struct unicam_fmt *formats;
> + unsigned int num_formats;
> +
> + if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + formats = unicam_image_formats;
> + num_formats = ARRAY_SIZE(unicam_image_formats);
> + } else {
> + formats = unicam_meta_formats;
> + num_formats = ARRAY_SIZE(unicam_meta_formats);
> + }
> +
> + if (code->index >= num_formats)
> + goto out;
> +
> + code->code = formats[code->index].code;
> + }
> +
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_sd_enable_streams(struct v4l2_subdev *sd, u32 pad,
> + u64 streams_mask)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_subdev_state *state;
> + u32 other_pad, other_stream;
> + int ret;
> +
> + if (WARN_ON(streams_mask != 1))
> + return -EINVAL;
> +
> + state = v4l2_subdev_lock_active_state(sd);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
> + &other_pad, &other_stream);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + if (ret)
> + return ret;
> +
> + unicam->sequence = 0;
> +
> + dev_dbg(unicam->dev, "Running with %u data lanes\n",
> + unicam->active_data_lanes);
> +
> + ret = clk_set_min_rate(unicam->vpu_clock, UNICAM_MIN_VPU_CLOCK_RATE);
> + if (ret) {
> + dev_err(unicam->dev, "failed to set up VPU clock\n");
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(unicam->vpu_clock);
> + if (ret) {
> + dev_err(unicam->dev, "Failed to enable VPU clock: %d\n", ret);
> + goto err_vpu_clock;
> + }
> +
> + ret = clk_set_rate(unicam->clock, 100 * 1000 * 1000);
> + if (ret) {
> + dev_err(unicam->dev, "failed to set up CSI clock\n");
> + goto err_vpu_prepare;
> + }
> +
> + ret = clk_prepare_enable(unicam->clock);
> + if (ret) {
> + dev_err(unicam->dev, "Failed to enable CSI clock: %d\n", ret);
> + goto err_vpu_prepare;
> + }
> +
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, enable_streams,
> + unicam->sensor.pad->index, BIT(other_stream));
> +
> + if (ret && ret == -ENOIOCTLCMD)
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, s_stream, 1);
> +
> + if (ret) {
> + dev_err(unicam->dev, "stream on failed in subdev\n");
> + goto err_enable_stream;
> + }
> +
> + unicam->subdev.streaming = true;
> +
> + return 0;
> +
> +err_enable_stream:
> + clk_disable_unprepare(unicam->clock);
> +err_vpu_prepare:
> + clk_disable_unprepare(unicam->vpu_clock);
> +err_vpu_clock:
> + if (clk_set_min_rate(unicam->vpu_clock, 0))
> + dev_err(unicam->dev, "failed to reset the VPU clock\n");
> + return ret;
> +}
> +
> +static int unicam_sd_disable_streams(struct v4l2_subdev *sd, u32 pad,
> + u64 streams_mask)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_subdev_state *state;
> + u32 other_pad, other_stream;
> + int ret;
> +
> + if (WARN_ON(streams_mask != 1))
> + return -EINVAL;
> +
> + state = v4l2_subdev_lock_active_state(sd);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
> + &other_pad, &other_stream);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + if (ret) {
> + dev_err(unicam->dev, "disable streams failed: %d\n", ret);
> + return ret;
> + }
> +
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, disable_streams,
> + unicam->sensor.pad->index, BIT(other_stream));
> +
> + if (ret && ret == -ENOIOCTLCMD) {
> + /* The sensor does not support disable streams. */
> + if (unicam->node[UNICAM_IMAGE_NODE].streaming &&
> + other_pad == unicam->node[UNICAM_METADATA_NODE].pad.index) {
> + /* We won't stop the sensor yet. */
> + unicam->subdev.streaming = false;
> + return 0;
> + }
> + /* There is no more the metadata node, we can stop. */
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, s_stream, 0);
> + }
> +
> + clk_disable_unprepare(unicam->clock);
> + if (clk_set_min_rate(unicam->vpu_clock, 0))
> + dev_err(unicam->dev, "failed to reset the VPU clock\n");
> + clk_disable_unprepare(unicam->vpu_clock);
> +
> + unicam->subdev.streaming = false;
> +
> + return 0;
> +}
> +
> +static int unicam_subdev_set_pad_format(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_format *format)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_mbus_framefmt *sink_format, *source_format;
> + const struct unicam_fmt *fmtinfo;
> + int ret = 0;
> +
> + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && unicam->subdev.streaming)
> + return -EBUSY;
> +
> + /* No transcoding, source and sink formats must match. */
> + if (unicam_sd_pad_is_source(format->pad))
> + return v4l2_subdev_get_fmt(sd, state, format);
> +
> + fmtinfo = unicam_find_format_by_code(format->format.code, format->pad);
> + if (!fmtinfo)
> + fmtinfo = &unicam_image_formats[0];
> +
> + if (format->pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + format->format.width = clamp_t(unsigned int,
> + format->format.width,
> + UNICAM_MIN_WIDTH,
> + UNICAM_MAX_WIDTH);
> + format->format.height = clamp_t(unsigned int,
> + format->format.height,
> + UNICAM_MIN_HEIGHT,
> + UNICAM_MAX_HEIGHT);
> + }
> + format->format.field = V4L2_FIELD_NONE;
> +
> + v4l2_subdev_lock_state(state);
> +
> + sink_format = v4l2_state_get_stream_format(state, format->pad,
> + format->stream);
> + source_format = v4l2_subdev_state_get_opposite_stream_format(state,
> + format->pad,
> + format->stream);
> + if (!sink_format || !source_format) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + *sink_format = format->format;
> + *source_format = format->format;
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_subdev_enum_frame_size(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_frame_size_enum *fse)
> +{
> + const struct unicam_fmt *fmtinfo;
> + u32 pad, stream;
> + int ret = -EINVAL;
> +
> + if (fse->index > 0)
> + return ret;
> +
> + v4l2_subdev_lock_state(state);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, fse->pad,
> + fse->stream, &pad,
> + &stream);
> + if (ret)
> + goto out;
> +
> + if (unicam_sd_pad_is_source(fse->pad)) {
> + /* No transcoding, source and sink formats must match. */
> + struct v4l2_mbus_framefmt *fmt;
> +
> + fmt = v4l2_state_get_stream_format(state, pad, stream);
> + if (!fmt)
> + goto out;
> +
> + if (fse->code != fmt->code)
> + goto out;
> +
> + fse->min_width = fmt->width;
> + fse->max_width = fmt->width;
> + fse->min_height = fmt->height;
> + fse->max_height = fmt->height;
> + } else {
> + fmtinfo = unicam_find_format_by_code(fse->code, pad);
> + if (!fmtinfo)
> + goto out;
> +
> + fse->min_width = UNICAM_MIN_WIDTH;
> + fse->max_width = UNICAM_MAX_WIDTH;
> + fse->min_height = UNICAM_MIN_HEIGHT;
> + fse->max_height = UNICAM_MAX_HEIGHT;
> + }
> +
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static const struct v4l2_subdev_video_ops unicam_subdev_video_ops = {
> + .enable_streams = unicam_sd_enable_streams,
> + .disable_streams = unicam_sd_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_pad_ops unicam_subdev_pad_ops = {
> + .init_cfg = unicam_subdev_init_cfg,
> + .enum_mbus_code = unicam_subdev_enum_mbus_code,
> + .get_fmt = v4l2_subdev_get_fmt,
> + .set_fmt = unicam_subdev_set_pad_format,
> + .set_routing = unicam_subdev_set_routing,
> + .enum_frame_size = unicam_subdev_enum_frame_size,
> +};
> +
> +static const struct v4l2_subdev_ops unicam_subdev_ops = {
> + .video = &unicam_subdev_video_ops,
> + .pad = &unicam_subdev_pad_ops,
> +};
> +
> +static const struct media_entity_operations unicam_subdev_media_ops = {
> + .link_validate = v4l2_subdev_link_validate,
> + .has_route = v4l2_subdev_has_route,
> +};
> +
> +static int unicam_init_and_register_subdev(struct unicam_device *unicam)
> +{
> + struct v4l2_subdev *sd = &unicam->subdev.sd;
> + int ret;
> +
> + v4l2_subdev_init(sd, &unicam_subdev_ops);
> + v4l2_set_subdevdata(sd, unicam);
> + unicam->subdev.sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> + unicam->subdev.sd.entity.ops = &unicam_subdev_media_ops;
> + unicam->subdev.sd.dev = unicam->dev;
> + unicam->subdev.sd.owner = THIS_MODULE;
> + unicam->subdev.sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_MULTIPLEXED;
> + snprintf(unicam->subdev.sd.name, sizeof(unicam->subdev.sd.name), "unicam-subdev");
> +
> + unicam->subdev.pads[UNICAM_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +
> + unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_IMAGE].flags = MEDIA_PAD_FL_SOURCE;
> + unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_METADATA].flags = MEDIA_PAD_FL_SOURCE;
> +
> + ret = media_entity_pads_init(&sd->entity,
> + ARRAY_SIZE(unicam->subdev.pads), unicam->subdev.pads);
> + if (ret) {
> + dev_err(unicam->dev, "Could not init media entity");
> + return ret;
> + }
> +
> + ret = v4l2_subdev_init_finalize(sd);
> + if (ret) {
> + dev_err(unicam->dev, "Could not finalize init for subdev");
> + return ret;
> + }
> +
> + return v4l2_device_register_subdev(&unicam->v4l2_dev, sd);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Videobuf2 Queue Operations
> + */
please drop
> +
> +static int unicam_queue_setup(struct vb2_queue *vq,
> + unsigned int *nbuffers,
> + unsigned int *nplanes,
> + unsigned int sizes[],
> + struct device *alloc_devs[])
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vq);
> + struct unicam_device *unicam = node->dev;
> + unsigned int size = is_image_node(node) ?
> + node->v_fmt.fmt.pix.sizeimage :
> + node->v_fmt.fmt.meta.buffersize;
> +
> + if (vq->num_buffers + *nbuffers < 3)
> + *nbuffers = 3 - vq->num_buffers;
> +
> + if (*nplanes) {
> + if (sizes[0] < size) {
> + dev_dbg(unicam->dev, "sizes[0] %i < size %u\n", sizes[0],
> + size);
> + return -EINVAL;
> + }
> + size = sizes[0];
> + }
> +
> + *nplanes = 1;
> + sizes[0] = size;
> +
> + return 0;
> +}
> +
> +static int unicam_buffer_prepare(struct vb2_buffer *vb)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
> + struct unicam_device *unicam = node->dev;
> + struct unicam_buffer *buf = to_unicam_buffer(vb);
> + unsigned long size;
> +
> + if (WARN_ON(!node->fmt))
> + return -EINVAL;
> +
> + size = is_image_node(node) ? node->v_fmt.fmt.pix.sizeimage :
> + node->v_fmt.fmt.meta.buffersize;
> + if (vb2_plane_size(vb, 0) < size) {
> + dev_dbg(unicam->dev, "data will not fit into plane (%lu < %lu)\n",
> + vb2_plane_size(vb, 0), size);
> + return -EINVAL;
> + }
> +
> + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
> + return 0;
> +}
> +
> +static void unicam_buffer_queue(struct vb2_buffer *vb)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
> + struct unicam_buffer *buf = to_unicam_buffer(vb);
> +
> + spin_lock_irq(&node->dma_queue_lock);
> + list_add_tail(&buf->list, &node->dma_queue);
> + spin_unlock_irq(&node->dma_queue_lock);
> +}
> +
> +static void unicam_return_buffers(struct unicam_node *node,
> + enum vb2_buffer_state state)
> +{
> + struct unicam_buffer *buf, *tmp;
> +
> + list_for_each_entry_safe(buf, tmp, &node->dma_queue, list) {
> + list_del(&buf->list);
> + vb2_buffer_done(&buf->vb.vb2_buf, state);
> + }
> +
> + if (node->cur_frm)
> + vb2_buffer_done(&node->cur_frm->vb.vb2_buf,
> + state);
> + if (node->next_frm && node->cur_frm != node->next_frm)
> + vb2_buffer_done(&node->next_frm->vb.vb2_buf,
> + state);
> +
> + node->cur_frm = NULL;
> + node->next_frm = NULL;
> +}
> +
> +static int unicam_video_check_format(struct unicam_node *node)
> +{
> + const struct v4l2_mbus_framefmt *format;
> + struct unicam_device *unicam = node->dev;
> + struct v4l2_subdev_state *state;
> + int ret = 0;
> +
> + state = v4l2_subdev_lock_active_state(&unicam->subdev.sd);
> +
> + format = v4l2_state_get_stream_format(state, UNICAM_SD_PAD_SOURCE_IMAGE, 0);
> + if (!format) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + if (node->fmt->code != format->code ||
> + node->v_fmt.fmt.pix.height != format->height ||
> + node->v_fmt.fmt.pix.width != format->width ||
> + node->v_fmt.fmt.pix.field != format->field) {
> + dev_dbg(unicam->dev, "(%u x %u) %08x %s != (%u x %u) %08x %s\n",
> + node->v_fmt.fmt.pix.width, node->v_fmt.fmt.pix.height,
> + node->fmt->code,
> + v4l2_field_names[node->v_fmt.fmt.pix.field],
> + format->width, format->height, format->code,
> + v4l2_field_names[format->field]);
> + ret = -EPIPE;
> + goto out;
> + }
> +
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_start_streaming(struct vb2_queue *vq, unsigned int count)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vq);
> + struct unicam_device *unicam = node->dev;
> + dma_addr_t buffer_addr[UNICAM_MAX_NODES] = { 0 };
> + struct unicam_buffer *buf;
> + struct v4l2_subdev_state *state;
> + unsigned long flags;
> + unsigned int i;
> + int ret;
> + u32 pad, stream;
> + u32 remote_pad_index = is_image_node(node) ? UNICAM_SD_PAD_SOURCE_IMAGE
> + : UNICAM_SD_PAD_SOURCE_METADATA;
> +
> + /* Look for the route for the given pad and stream. */
> + state = v4l2_subdev_lock_active_state(&unicam->subdev.sd);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
> + remote_pad_index, 0,
> + &pad, &stream);
> + if (ret)
> + goto err_streaming;
> +
> + v4l2_subdev_unlock_state(state);
> +
> + dev_dbg(unicam->dev, "Starting stream 0 on pad %d on subdev %s\n",
> + remote_pad_index, unicam->subdev.sd.name);
> +
> + /* The metadata node can't be started alone. */
> + if (is_metadata_node(node)) {
> + if (!unicam->node[UNICAM_IMAGE_NODE].streaming) {
> + dev_err(unicam->dev,
> + "Can't start metadata without image\n");
> + return -EINVAL;
> + }
> + dev_dbg(unicam->dev, "starting metadata node\n");
> +
> + spin_lock_irqsave(&node->dma_queue_lock, flags);
> + buf = list_first_entry(&node->dma_queue,
> + struct unicam_buffer, list);
> + dev_dbg(unicam->dev, "buffer %d: %p\n", i, buf);
> + node->cur_frm = buf;
> + node->next_frm = buf;
> + list_del(&buf->list);
> + spin_unlock_irqrestore(&node->dma_queue_lock, flags);
> +
> + buffer_addr[UNICAM_METADATA_NODE] =
> + vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> + dev_dbg(unicam->dev, "buffer %d addr: %lld\n", i, buffer_addr[i]);
> +
> + unicam_start_metadata(unicam, buffer_addr);
> + node->streaming = true;
> + return 0;
> + }
> +
> + ret = pm_runtime_get_sync(unicam->dev);
> + if (ret < 0) {
> + dev_err(unicam->dev, "pm_runtime_get_sync failed\n");
> + goto err_streaming;
> + }
> +
> + ret = media_pipeline_start(node->video_dev.entity.pads, &unicam->pipe);
> + if (ret < 0) {
> + dev_dbg(unicam->dev, "Failed to start media pipeline: %d\n", ret);
> + goto err_pm_put;
> + }
> +
> + ret = unicam_video_check_format(node);
> + if (ret < 0) {
> + dev_err(unicam->dev, "Video format is incorrect: %d\n", ret);
> + goto error_pipeline;
> + }
> +
> + dev_dbg(unicam->dev, "node %s\n", node->video_dev.name);
> +
> + spin_lock_irqsave(&node->dma_queue_lock, flags);
> + buf = list_first_entry(&node->dma_queue,
> + struct unicam_buffer, list);
> + dev_dbg(unicam->dev, "buffer %d: %p\n", i, buf);
> + node->cur_frm = buf;
> + node->next_frm = buf;
> + list_del(&buf->list);
> + spin_unlock_irqrestore(&node->dma_queue_lock, flags);
> +
> + buffer_addr[UNICAM_IMAGE_NODE] =
> + vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> +
> + dev_dbg(unicam->dev, "Start unicam\n");
> + unicam_start_rx(unicam, buffer_addr);
> +
> + dev_dbg(unicam->dev, "Enable stream\n");
> + ret = v4l2_subdev_call(&unicam->subdev.sd, video, enable_streams,
> + remote_pad_index, BIT(0));
> + if (ret < 0) {
> + dev_err(unicam->dev, "stream on failed in subdev\n");
> + goto error_pipeline;
> + }
> +
> + node->streaming = true;
> +
> + return 0;
> +
> +error_pipeline:
> + media_pipeline_stop(node->video_dev.entity.pads);
> +err_pm_put:
> + pm_runtime_put_sync(unicam->dev);
> +err_streaming:
> + v4l2_subdev_unlock_state(state);
> + return ret;
> +}
> +
> +static void unicam_stop_streaming(struct vb2_queue *vq)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vq);
> + struct unicam_device *unicam = node->dev;
> + u32 remote_pad_index = is_image_node(node) ? UNICAM_SD_PAD_SOURCE_IMAGE
> + : UNICAM_SD_PAD_SOURCE_METADATA;
> +
> + node->streaming = false;
> +
> + v4l2_subdev_call(&unicam->subdev.sd, video, disable_streams,
> + remote_pad_index, BIT(0));
> +
> + /* We can stream only with the image node. */
> + if (is_metadata_node(node)) {
> + /*
> + * Allow the hardware to spin in the dummy buffer.
> + * This is only really needed if the embedded data pad is
> + * disabled before the image pad.
> + */
> + unicam_wr_dma_addr(node, node->dummy_buf_dma_addr,
> + UNICAM_DUMMY_BUF_SIZE);
> + goto dequeue_buffers;
> + }
> +
> + unicam_disable(unicam);
> +
> + media_pipeline_stop(node->video_dev.entity.pads);
> + pm_runtime_put(unicam->dev);
> +
> +dequeue_buffers:
> + /* Clear all queued buffers for the node */
> + unicam_return_buffers(node, VB2_BUF_STATE_ERROR);
> +}
> +
> +static const struct vb2_ops unicam_video_qops = {
> + .wait_prepare = vb2_ops_wait_prepare,
> + .wait_finish = vb2_ops_wait_finish,
> + .queue_setup = unicam_queue_setup,
> + .buf_prepare = unicam_buffer_prepare,
> + .buf_queue = unicam_buffer_queue,
> + .start_streaming = unicam_start_streaming,
> + .stop_streaming = unicam_stop_streaming,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 video device Operations
> + */
please drop
> +
> +static int unicam_querycap(struct file *file, void *priv,
> + struct v4l2_capability *cap)
> +{
> + struct unicam_node *node = video_drvdata(file);
> + struct unicam_device *unicam = node->dev;
> +
> + strscpy(cap->driver, UNICAM_MODULE_NAME, sizeof(cap->driver));
> + strscpy(cap->card, UNICAM_MODULE_NAME, sizeof(cap->card));
> +
> + snprintf(cap->bus_info, sizeof(cap->bus_info),
> + "platform:%s", dev_name(unicam->dev));
> +
> + cap->capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE;
> +
> + return 0;
> +}
> +
> +static int unicam_log_status(struct file *file, void *fh)
> +{
> + struct unicam_node *node = video_drvdata(file);
> + struct unicam_device *unicam = node->dev;
> + u32 reg;
> +
> + /* status for sub devices */
> + v4l2_device_call_all(&unicam->v4l2_dev, 0, core, log_status);
> +
> + dev_info(unicam->dev, "-----Receiver status-----\n");
> + dev_info(unicam->dev, "V4L2 width/height: %ux%u\n",
> + node->v_fmt.fmt.pix.width, node->v_fmt.fmt.pix.height);
> + dev_info(unicam->dev, "Mediabus format: %08x\n", node->fmt->code);
> + dev_info(unicam->dev, "V4L2 format: %08x\n",
> + node->v_fmt.fmt.pix.pixelformat);
> + reg = unicam_reg_read(unicam, UNICAM_IPIPE);
> + dev_info(unicam->dev, "Unpacking/packing: %u / %u\n",
> + unicam_get_field(reg, UNICAM_PUM_MASK),
> + unicam_get_field(reg, UNICAM_PPM_MASK));
> + dev_info(unicam->dev, "----Live data----\n");
> + dev_info(unicam->dev, "Programmed stride: %4u\n",
> + unicam_reg_read(unicam, UNICAM_IBLS));
> + dev_info(unicam->dev, "Detected resolution: %ux%u\n",
> + unicam_reg_read(unicam, UNICAM_IHSTA),
> + unicam_reg_read(unicam, UNICAM_IVSTA));
> + dev_info(unicam->dev, "Write pointer: %08x\n",
> + unicam_reg_read(unicam, UNICAM_IBWP));
> +
> + return 0;
> +}
> +
> +static int unicam_subscribe_event(struct v4l2_fh *fh,
> + const struct v4l2_event_subscription *sub)
> +{
> + switch (sub->type) {
> + case V4L2_EVENT_FRAME_SYNC:
> + return v4l2_event_subscribe(fh, sub, 2, NULL);
> + }
> +
> + return v4l2_ctrl_subscribe_event(fh, sub);
> +}
> +
> +static int unicam_enum_fmt_vid(struct file *file, void *priv,
> + struct v4l2_fmtdesc *f)
> +{
> + unsigned int i, index;
> +
> + for (i = 0, index = 0; i < ARRAY_SIZE(unicam_image_formats); i++) {
> + if (f->mbus_code && unicam_image_formats[i].code != f->mbus_code)
> + continue;
> +
> + if (index == f->index) {
> + f->pixelformat = unicam_image_formats[i].fourcc;
> + return 0;
> + }
> +
> + index++;
> +
> + if (unicam_image_formats[i].unpacked_fourcc) {
> + if (index == f->index) {
> + f->pixelformat = unicam_image_formats[i].unpacked_fourcc;
> + return 0;
> + }
> + index++;
> + }
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int unicam_g_fmt_vid(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> +
> + *f = node->v_fmt;
> +
> + return 0;
> +}
> +
> +static const struct unicam_fmt *
> +unicam_try_fmt(struct unicam_node *node, struct v4l2_format *f)
> +{
> + struct v4l2_pix_format *v4l2_format = &f->fmt.pix;
> + struct unicam_device *unicam = node->dev;
> + const struct unicam_fmt *fmt;
> +
> + /*
> + * Default to the first format if the requested pixel format code isn't
> + * supported.
> + */
> + fmt = unicam_find_format_by_fourcc(v4l2_format->pixelformat,
> + UNICAM_SD_PAD_SOURCE_IMAGE);
> + if (!fmt) {
> + fmt = &unicam_image_formats[0];
> + v4l2_format->pixelformat = fmt->fourcc;
> + }
> +
> + unicam_calc_format_size_bpl(unicam, fmt, f);
> +
> + if (v4l2_format->field == V4L2_FIELD_ANY)
> + v4l2_format->field = V4L2_FIELD_NONE;
> +
> + dev_dbg(unicam->dev, "%s: %08x %ux%u (bytesperline %u sizeimage %u)\n",
> + __func__, v4l2_format->pixelformat,
> + v4l2_format->width, v4l2_format->height,
> + v4l2_format->bytesperline, v4l2_format->sizeimage);
> +
> + return fmt;
> +}
> +
> +static int unicam_try_fmt_vid(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> +
> + unicam_try_fmt(node, f);
> + return 0;
> +}
> +
> +static int unicam_s_fmt_vid(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> + struct unicam_device *unicam = node->dev;
> +
> + if (vb2_is_busy(&node->buffer_queue)) {
> + dev_dbg(unicam->dev, "%s device busy\n", __func__);
> + return -EBUSY;
> + }
> +
> + node->fmt = unicam_try_fmt(node, f);
> +
> + node->v_fmt = *f;
> +
> + return 0;
> +}
> +
> +static int unicam_enum_framesizes(struct file *file, void *fh,
> + struct v4l2_frmsizeenum *fsize)
> +{
> + struct unicam_node *node = video_drvdata(file);
> + int ret = -EINVAL;
> +
> + if (fsize->index > 0)
> + return ret;
> +
> + if (is_image_node(node)) {
> + if (!unicam_find_format_by_fourcc(fsize->pixel_format,
> + UNICAM_SD_PAD_SOURCE_IMAGE))
> + return ret;
> +
> + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> + fsize->stepwise.min_width = UNICAM_MIN_WIDTH;
> + fsize->stepwise.max_width = UNICAM_MAX_WIDTH;
> + fsize->stepwise.step_width = 1;
> + fsize->stepwise.min_height = UNICAM_MIN_HEIGHT;
> + fsize->stepwise.max_height = UNICAM_MAX_HEIGHT;
> + fsize->stepwise.step_height = 1;
> + } else {
> + if (!unicam_find_format_by_fourcc(fsize->pixel_format,
> + UNICAM_SD_PAD_SOURCE_METADATA))
> + return ret;
> +
> + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
> + fsize->discrete.width = UNICAM_EMBEDDED_SIZE;
> + fsize->discrete.height = 1;
> + }
> +
> + return 0;
> +}
> +
> +static int unicam_enum_fmt_meta(struct file *file, void *priv,
> + struct v4l2_fmtdesc *f)
> +{
> + unsigned int i, index;
> +
> + for (i = 0, index = 0; i < ARRAY_SIZE(unicam_meta_formats); i++) {
> + if (f->mbus_code && unicam_meta_formats[i].code != f->mbus_code)
> + continue;
> + if (!unicam_meta_formats[i].metadata_fmt)
> + continue;
> +
> + if (index == f->index) {
> + f->pixelformat = unicam_meta_formats[i].fourcc;
> + f->type = V4L2_BUF_TYPE_META_CAPTURE;
> + return 0;
> + }
> + index++;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int unicam_g_fmt_meta(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> +
> + f->fmt.meta = node->v_fmt.fmt.meta;
> +
> + return 0;
> +}
> +
> +static int unicam_try_fmt_meta(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + f->fmt.meta.dataformat = V4L2_META_FMT_8;
> + f->fmt.meta.buffersize = UNICAM_EMBEDDED_SIZE;
> +
> + return 0;
> +}
> +
> +static int unicam_s_fmt_meta(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> +
> + unicam_try_fmt_meta(file, priv, f);
> +
> + node->v_fmt = *f;
> +
> + return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops unicam_ioctl_ops = {
> + .vidioc_querycap = unicam_querycap,
> + .vidioc_enum_fmt_vid_cap = unicam_enum_fmt_vid,
> + .vidioc_g_fmt_vid_cap = unicam_g_fmt_vid,
> + .vidioc_try_fmt_vid_cap = unicam_try_fmt_vid,
> + .vidioc_s_fmt_vid_cap = unicam_s_fmt_vid,
> +
> + .vidioc_enum_fmt_meta_cap = unicam_enum_fmt_meta,
> + .vidioc_g_fmt_meta_cap = unicam_g_fmt_meta,
> + .vidioc_try_fmt_meta_cap = unicam_try_fmt_meta,
> + .vidioc_s_fmt_meta_cap = unicam_s_fmt_meta,
> +
> + .vidioc_enum_framesizes = unicam_enum_framesizes,
> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
> + .vidioc_create_bufs = vb2_ioctl_create_bufs,
> + .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> + .vidioc_querybuf = vb2_ioctl_querybuf,
> + .vidioc_qbuf = vb2_ioctl_qbuf,
> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
> + .vidioc_expbuf = vb2_ioctl_expbuf,
> + .vidioc_streamon = vb2_ioctl_streamon,
> + .vidioc_streamoff = vb2_ioctl_streamoff,
> +
> + .vidioc_log_status = unicam_log_status,
> + .vidioc_subscribe_event = unicam_subscribe_event,
> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +/* unicam capture driver file operations */
> +static const struct v4l2_file_operations unicam_fops = {
> + .owner = THIS_MODULE,
> + .open = v4l2_fh_open,
> + .release = vb2_fop_release,
> + .poll = vb2_fop_poll,
> + .unlocked_ioctl = video_ioctl2,
> + .mmap = vb2_fop_mmap,
> +};
> +
> +static void unicam_set_default_format(struct unicam_node *node)
> +{
> + if (is_image_node(node)) {
> + struct v4l2_pix_format *pix_fmt = &node->v_fmt.fmt.pix;
> +
> + pix_fmt->width = default_image_format.width;
> + pix_fmt->height = default_image_format.height;
> + pix_fmt->field = default_image_format.field;
> + pix_fmt->colorspace = default_image_format.colorspace;
> + pix_fmt->ycbcr_enc = default_image_format.ycbcr_enc;
> + pix_fmt->quantization = default_image_format.quantization;
> + pix_fmt->xfer_func = default_image_format.xfer_func;
> + pix_fmt->pixelformat = unicam_image_formats[0].fourcc;
> + unicam_calc_format_size_bpl(node->dev, &unicam_image_formats[0],
> + &node->v_fmt);
> + node->v_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +
> + node->fmt = &unicam_image_formats[0];
> + } else {
> + const struct unicam_fmt *fmt;
> +
> + /* Fix this node format as embedded data. */
> + fmt = &unicam_meta_formats[0];
> + node->fmt = fmt;
> + node->v_fmt.fmt.meta.dataformat = fmt->fourcc;
> + node->v_fmt.fmt.meta.buffersize = UNICAM_EMBEDDED_SIZE;
> + node->v_fmt.type = V4L2_BUF_TYPE_META_CAPTURE;
> + }
> +}
> +
> +static int unicam_register_node(struct unicam_device *unicam,
> + enum unicam_node_type type)
> +{
> + struct video_device *vdev;
> + struct vb2_queue *q;
> + struct unicam_node *node = &unicam->node[type];
> + int ret;
> + u32 pad_index = type == UNICAM_IMAGE_NODE ? UNICAM_SD_PAD_SOURCE_IMAGE
> + : UNICAM_SD_PAD_SOURCE_METADATA;
> +
> + node->dev = unicam_get(unicam);
> + node->id = type;
> +
> + spin_lock_init(&node->dma_queue_lock);
> + mutex_init(&node->lock);
> +
> + INIT_LIST_HEAD(&node->dma_queue);
> +
> + /* Initialize the videobuf2 queue. */
> + q = &node->buffer_queue;
> + q->type = type == UNICAM_IMAGE_NODE ? V4L2_BUF_TYPE_VIDEO_CAPTURE
> + : V4L2_BUF_TYPE_META_CAPTURE;
> + q->io_modes = VB2_MMAP | VB2_DMABUF;
> + q->drv_priv = node;
> + q->ops = &unicam_video_qops;
> + q->mem_ops = &vb2_dma_contig_memops;
> + q->buf_struct_size = sizeof(struct unicam_buffer);
> + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> + q->lock = &node->lock;
> + q->min_buffers_needed = 1;
> + q->dev = unicam->dev;
> +
> + ret = vb2_queue_init(q);
> + if (ret) {
> + dev_err(unicam->dev, "vb2_queue_init() failed\n");
> + goto err_unicam_put;
> + }
> +
> + /* Initialize the video device. */
> + vdev = &node->video_dev;
> + vdev->release = unicam_node_release;
> + vdev->fops = &unicam_fops;
> + vdev->ioctl_ops = &unicam_ioctl_ops;
> + vdev->v4l2_dev = &unicam->v4l2_dev;
> + vdev->vfl_dir = VFL_DIR_RX;
> + vdev->queue = q;
> + vdev->lock = &node->lock;
> + vdev->device_caps = type == UNICAM_IMAGE_NODE ?
> + V4L2_CAP_VIDEO_CAPTURE : V4L2_CAP_META_CAPTURE;
> + vdev->device_caps |= V4L2_CAP_STREAMING | V4L2_CAP_IO_MC;
> +
> + /* Define the device names */
> + snprintf(vdev->name, sizeof(vdev->name), "%s-%s", UNICAM_MODULE_NAME,
> + type == UNICAM_IMAGE_NODE ? "image" : "embedded");
> +
> + video_set_drvdata(vdev, node);
> + if (type == UNICAM_IMAGE_NODE)
> + vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT;
> + node->pad.flags = MEDIA_PAD_FL_SINK;
> + ret = media_entity_pads_init(&vdev->entity, 1, &node->pad);
> + if (ret)
> + goto err_unicam_put;
> +
> + node->dummy_buf_cpu_addr = dma_alloc_coherent(unicam->dev,
> + UNICAM_DUMMY_BUF_SIZE,
> + &node->dummy_buf_dma_addr,
> + GFP_KERNEL);
> + if (!node->dummy_buf_cpu_addr) {
> + dev_err(unicam->dev, "Unable to allocate dummy buffer.\n");
> + ret = -ENOMEM;
> + goto err_entity_cleanup;
> + }
> +
> + unicam_set_default_format(node);
> +
> + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> + if (ret) {
> + dev_err(unicam->dev, "Unable to register video device %s\n",
> + vdev->name);
> + goto err_dma_free;
> + }
> +
> + node->registered = true;
> +
> + ret = media_create_pad_link(&unicam->subdev.sd.entity,
> + pad_index,
> + &node->video_dev.entity,
> + 0,
> + MEDIA_LNK_FL_ENABLED |
> + MEDIA_LNK_FL_IMMUTABLE);
> + if (ret) {
> + /*
> + * No need for cleanup, the caller will unregister the
> + * video device, which will drop the reference on the
> + * device and trigger the cleanup.
> + */
> + dev_err(unicam->dev, "Unable to create pad link for %s",
> + unicam->sensor.subdev->name);
> + return ret;
> + }
> +
> + return 0;
> +
> +err_dma_free:
> + dma_free_coherent(unicam->dev, UNICAM_DUMMY_BUF_SIZE,
> + node->dummy_buf_cpu_addr,
> + node->dummy_buf_dma_addr);
> +err_entity_cleanup:
> + media_entity_cleanup(&vdev->entity);
> +err_unicam_put:
> + unicam_put(unicam);
> + return ret;
> +}
> +
> +static void unicam_unregister_nodes(struct unicam_device *unicam)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + struct unicam_node *node = &unicam->node[i];
> +
> + if (node->dummy_buf_cpu_addr)
> + dma_free_coherent(unicam->dev, UNICAM_DUMMY_BUF_SIZE,
> + node->dummy_buf_cpu_addr,
> + node->dummy_buf_dma_addr);
> +
> + if (node->registered) {
> + video_unregister_device(&node->video_dev);
> + node->registered = false;
> + }
> + }
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 async notifier
> + */

please drop

Best regards



2022-02-14 09:25:58

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Hi Stefan,

On Sun, Feb 13, 2022 at 12:17:25PM +0100, Stefan Wahren wrote:
> Am 08.02.22 um 16:50 schrieb Jean-Michel Hautbois:
> > Add driver for the Unicam camera receiver block on BCM283x processors.
> > It is represented as two video device nodes: unicam-image and
> > unicam-embedded which are connected to an internal subdev (named
> > unicam-subdev) in order to manage streams routing.
> >
> > Signed-off-by: Dave Stevenson <[email protected]>
> > Signed-off-by: Naushir Patuck <[email protected]>
> > Signed-off-by: Jean-Michel Hautbois <[email protected]>
> >
> > ---
> > v4:
> > - Add the vendor prefox for DT name
> > - Use the reg-names in DT parsing
> > - Remove MAINTAINERS entry
> >
> > v3 main changes:
> > - Change code organization
> > - Remove unused variables
> > - Correct the fmt_meta functions
> > - Rewrite the start/stop streaming
> > - You can now start the image node alone, but not the metadata one
> > - The buffers are allocated per-node
> > - only the required stream is started, if the route exists and is
> > enabled
> > - Prefix the macros with UNICAM_ to not have too generic names
> > - Drop colorspace support
> > -> This is causing issues in the try-fmt v4l2-compliance test
> > test VIDIOC_G_FMT: OK
> > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > test VIDIOC_TRY_FMT: FAIL
> > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > test VIDIOC_S_FMT: FAIL
> >
> > v2: Remove the unicam_{info,debug,error} macros and use
> > dev_dbg/dev_err instead.
> > ---
> > drivers/media/platform/Kconfig | 1 +
> > drivers/media/platform/Makefile | 2 +
> > drivers/media/platform/bcm2835/Kconfig | 21 +
> > drivers/media/platform/bcm2835/Makefile | 3 +
> > .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> > .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> > 6 files changed, 2850 insertions(+)
> > create mode 100644 drivers/media/platform/bcm2835/Kconfig
> > create mode 100644 drivers/media/platform/bcm2835/Makefile
> > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c
> >
> > diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> > index 9fbdba0fd1e7..033b0358fbb8 100644
> > --- a/drivers/media/platform/Kconfig
> > +++ b/drivers/media/platform/Kconfig
> > @@ -170,6 +170,7 @@ source "drivers/media/platform/am437x/Kconfig"
> > source "drivers/media/platform/xilinx/Kconfig"
> > source "drivers/media/platform/rcar-vin/Kconfig"
> > source "drivers/media/platform/atmel/Kconfig"
> > +source "drivers/media/platform/bcm2835/Kconfig"
> > source "drivers/media/platform/sunxi/Kconfig"
> >
> > config VIDEO_TI_CAL
> > diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> > index 28eb4aadbf45..18894fc586aa 100644
> > --- a/drivers/media/platform/Makefile
> > +++ b/drivers/media/platform/Makefile
> > @@ -86,6 +86,8 @@ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom/camss/
> >
> > obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
> >
> > +obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835/
> > +
> > obj-y += sunxi/
> >
> > obj-$(CONFIG_VIDEO_MESON_GE2D) += meson/ge2d/
> > diff --git a/drivers/media/platform/bcm2835/Kconfig b/drivers/media/platform/bcm2835/Kconfig
> > new file mode 100644
> > index 000000000000..1691541da905
> > --- /dev/null
> > +++ b/drivers/media/platform/bcm2835/Kconfig
> > @@ -0,0 +1,21 @@
> > +# Broadcom VideoCore4 V4L2 camera support
> > +
> > +config VIDEO_BCM2835_UNICAM
> > + tristate "Broadcom BCM283x/BCM271x Unicam video capture driver"
> > + depends on ARCH_BCM2835 || COMPILE_TEST
> > + depends on VIDEO_V4L2
> > + select MEDIA_CONTROLLER
> > + select VIDEO_V4L2_SUBDEV_API
> > + select V4L2_FWNODE
> > + select VIDEOBUF2_DMA_CONTIG
> > + help
> > + Say Y here to enable support for the BCM283x/BCM271x CSI-2 receiver.
> > + This is a V4L2 driver that controls the CSI-2 receiver directly,
> > + independently from the VC4 firmware.
> > + This driver is mutually exclusive with the use of bcm2835-camera. The
> > + firmware will disable all access to the peripheral from within the
> > + firmware if it finds a DT node using it, and bcm2835-camera will
> > + therefore fail to probe.
>
> would be nice to know, what is the exact condition for the firmware. The
> compatible and must its status be enabled?
>
> > +
> > + To compile this driver as a module, choose M here. The module will be
> > + called bcm2835-unicam.

[snip]

> > diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam.c b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> > new file mode 100644
> > index 000000000000..470e691637c7
> > --- /dev/null
> > +++ b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> > @@ -0,0 +1,2570 @@

[snip]

> > +static inline bool unicam_sd_pad_is_sink(u32 pad)
> > +{
> > + /* Camera RX has 1 sink pad, and N source pads */
> > + return pad == 0;
> > +}
>
> s/0/UNICAM_SD_PAD_SINK ?

Good idea.

> > +
> > +static inline bool unicam_sd_pad_is_source(u32 pad)
> > +{
> > + /* Camera RX has 1 sink pad, and N source pads */
> > + return pad != UNICAM_SD_PAD_SINK;
> > +}
> > +
> > +enum unicam_node_type {
> > + UNICAM_IMAGE_NODE,
> > + UNICAM_METADATA_NODE,
> > + UNICAM_MAX_NODES
> > +};
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Structure definitions
> > + */
>
> please drop

I really favour splitting code in sections, it makes it more readable.
At the end of the day I think this is a personal decision for the
author, if no specific policy is enforced at the subsystem level.

[snip]

> > +/*
> > + * unicam_isr : ISR handler for unicam capture
> > + * @irq: irq number
> > + * @dev_id: dev_id ptr
>
> these kernel-doc doesn't match with the definition. dev seems to be the
> unicam instance?

I'd drop the comment as it doesn't really contain much useful information.

> > + *
> > + * It changes status of the captured buffer, takes next buffer from the queue
> > + * and sets its address in unicam registers
> > + */
> > +static irqreturn_t unicam_isr(int irq, void *dev)

[snip]

--
Regards,

Laurent Pinchart

2022-02-16 21:26:17

by Stefan Wahren

[permalink] [raw]
Subject: Re: [PATCH v5 00/11] Add support for BCM2835 camera interface (unicam)

Hi Jean-Michel,

Am 08.02.22 um 16:50 schrieb Jean-Michel Hautbois:
> Hello !
>
> ...
>
> In order to properly configure the media pipeline, it is needed to call
> the usual ioctls, and configure routing in order to send the embedded
> data from the sensor to the "unicam-embedded" device node :
>
> ```
> media=0
> media-ctl -d${media} -l "'imx219 2-0010':0->'unicam-subdev':0 [1]"
> media-ctl -d${media} -l "'unicam-subdev':1->'unicam-image':0 [1]"
> media-ctl -d${media} -v -R "'unicam-subdev' [0/0->1/0[1],0/1->2/0[1]]"
> media-ctl -d${media} -V "'imx219 2-0010':0/0 [fmt:SRGGB10_1X10/3280x2464 field:none]"
> v4l2-ctl -d0 --set-fmt-video width=3280,height=2464,pixelformat='pRAA',field=none
> media-ctl -d${media} -v -V "'imx219 2-0010':0/1 [fmt:METADATA_8/16384x1 field:none]"
> media-ctl -d${media} -p
> ```

i tried to test the unicam driver on a Raspberry Pi 4 with the imx219
camera (based on 5.17-rc4). The unicam & imx219 driver probes and
/dev/video0 is created.

If a execute the first media-ctl command, it complains with invalid
argument 22. Is there a more fool-proof variant to configure this (a
script or something else)? I never used the unicam driver before.

Here is the output of

$ mediactl -d0 -p

Media controller API version 5.17.0

Media device information
------------------------
driver          unicam
model           unicam
serial         
bus info        platform:fe801000.csi
hw revision     0x0
driver version  5.17.0

Device topology
- entity 1: unicam-subdev (3 pads, 3 links, 2 routes)
            type V4L2 subdev subtype Unknown flags 0
            device node name /dev/v4l-subdev0
    routes:
        0/0 -> 1/0 [ACTIVE]
        0/1 -> 2/0 [ACTIVE]
    pad0: Sink
        [stream:0 fmt:unknown/0x0]
        [stream:1 fmt:unknown/0x0]
        <- "imx219 5-0010":0 [ENABLED,IMMUTABLE]
    pad1: Source
        [stream:0 fmt:unknown/0x0]
        -> "unicam-image":0 [ENABLED,IMMUTABLE]
    pad2: Source
        [stream:0 fmt:unknown/0x0]
        -> "unicam-embedded":0 [ENABLED,IMMUTABLE]

- entity 5: imx219 5-0010 (1 pad, 1 link, 2 routes)
            type V4L2 subdev subtype Sensor flags 0
            device node name /dev/v4l-subdev1
    routes:
        0/0 -> 0/0 [ACTIVE, IMMUTABLE, SOURCE]
        0/0 -> 0/1 [ACTIVE, SOURCE]
    pad0: Source
        [stream:0 fmt:SRGGB10_1X10/3280x2464 field:none colorspace:raw
         crop.bounds:(8,8)/3280x2464
         crop:(8,8)/3280x2464]
        [stream:1 fmt:METADATA_8/16384x1 field:none
         crop.bounds:(8,8)/3280x2464
         crop:(8,8)/3280x2464]
        -> "unicam-subdev":0 [ENABLED,IMMUTABLE]

- entity 9: unicam-image (1 pad, 1 link, 0 route)
            type Node subtype V4L flags 1
            device node name /dev/video0
    pad0: Sink
        <- "unicam-subdev":1 [ENABLED,IMMUTABLE]

- entity 15: unicam-embedded (1 pad, 1 link, 0 route)
             type Node subtype V4L flags 0
             device node name /dev/video1
    pad0: Sink
        <- "unicam-subdev":2 [ENABLED,IMMUTABLE]

2022-02-20 15:16:46

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Hello,

On Sun, Feb 20, 2022 at 11:01:26AM +0100, Stefan Wahren wrote:
> Am 08.02.22 um 16:50 schrieb Jean-Michel Hautbois:
> > Add driver for the Unicam camera receiver block on BCM283x processors.
> > It is represented as two video device nodes: unicam-image and
> > unicam-embedded which are connected to an internal subdev (named
> > unicam-subdev) in order to manage streams routing.
> >
> > Signed-off-by: Dave Stevenson <[email protected]>
> > Signed-off-by: Naushir Patuck <[email protected]>
> > Signed-off-by: Jean-Michel Hautbois <[email protected]>
> >
> > ---
> > v4:
> > - Add the vendor prefox for DT name
> > - Use the reg-names in DT parsing
> > - Remove MAINTAINERS entry
> >
> > v3 main changes:
> > - Change code organization
> > - Remove unused variables
> > - Correct the fmt_meta functions
> > - Rewrite the start/stop streaming
> > - You can now start the image node alone, but not the metadata one
> > - The buffers are allocated per-node
> > - only the required stream is started, if the route exists and is
> > enabled
> > - Prefix the macros with UNICAM_ to not have too generic names
> > - Drop colorspace support
> > -> This is causing issues in the try-fmt v4l2-compliance test
> > test VIDIOC_G_FMT: OK
> > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > test VIDIOC_TRY_FMT: FAIL
> > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > test VIDIOC_S_FMT: FAIL
> >
> > v2: Remove the unicam_{info,debug,error} macros and use
> > dev_dbg/dev_err instead.
> > ---
> > drivers/media/platform/Kconfig | 1 +
> > drivers/media/platform/Makefile | 2 +
> > drivers/media/platform/bcm2835/Kconfig | 21 +
> > drivers/media/platform/bcm2835/Makefile | 3 +
> > .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> > .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> > 6 files changed, 2850 insertions(+)
> > create mode 100644 drivers/media/platform/bcm2835/Kconfig
> > create mode 100644 drivers/media/platform/bcm2835/Makefile
> > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c
> >
> > diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> > index 9fbdba0fd1e7..033b0358fbb8 100644
> > --- a/drivers/media/platform/Kconfig
> > +++ b/drivers/media/platform/Kconfig
> > @@ -170,6 +170,7 @@ source "drivers/media/platform/am437x/Kconfig"
> > source "drivers/media/platform/xilinx/Kconfig"
> > source "drivers/media/platform/rcar-vin/Kconfig"
> > source "drivers/media/platform/atmel/Kconfig"
> > +source "drivers/media/platform/bcm2835/Kconfig"
> > source "drivers/media/platform/sunxi/Kconfig"
> >
> > config VIDEO_TI_CAL
> > diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> > index 28eb4aadbf45..18894fc586aa 100644
> > --- a/drivers/media/platform/Makefile
> > +++ b/drivers/media/platform/Makefile
> > @@ -86,6 +86,8 @@ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom/camss/
> >
> > obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
> >
> > +obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835/
> > +
> > obj-y += sunxi/
> >
> > obj-$(CONFIG_VIDEO_MESON_GE2D) += meson/ge2d/
> > diff --git a/drivers/media/platform/bcm2835/Kconfig b/drivers/media/platform/bcm2835/Kconfig
> > new file mode 100644
> > index 000000000000..1691541da905
> > --- /dev/null
> > +++ b/drivers/media/platform/bcm2835/Kconfig
> > @@ -0,0 +1,21 @@
> > +# Broadcom VideoCore4 V4L2 camera support
> > +
> > +config VIDEO_BCM2835_UNICAM
> > + tristate "Broadcom BCM283x/BCM271x Unicam video capture driver"
> > + depends on ARCH_BCM2835 || COMPILE_TEST
> > + depends on VIDEO_V4L2
> > + select MEDIA_CONTROLLER
> > + select VIDEO_V4L2_SUBDEV_API
> > + select V4L2_FWNODE
> > + select VIDEOBUF2_DMA_CONTIG
> > + help
> > + Say Y here to enable support for the BCM283x/BCM271x CSI-2 receiver.
> > + This is a V4L2 driver that controls the CSI-2 receiver directly,
> > + independently from the VC4 firmware.
> > + This driver is mutually exclusive with the use of bcm2835-camera. The
> > + firmware will disable all access to the peripheral from within the
> > + firmware if it finds a DT node using it, and bcm2835-camera will
> > + therefore fail to probe.
> > +
> > + To compile this driver as a module, choose M here. The module will be
> > + called bcm2835-unicam.
> > diff --git a/drivers/media/platform/bcm2835/Makefile b/drivers/media/platform/bcm2835/Makefile
> > new file mode 100644
> > index 000000000000..a98aba03598a
> > --- /dev/null
> > +++ b/drivers/media/platform/bcm2835/Makefile
> > @@ -0,0 +1,3 @@
> > +# Makefile for BCM2835 Unicam driver
> > +
> > +obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835-unicam.o
> > diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > new file mode 100644
> > index 000000000000..b8d297076a02
> > --- /dev/null
> > +++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > @@ -0,0 +1,253 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +
> > +/*
> > + * Copyright (C) 2017-2020 Raspberry Pi Trading.
> > + * Dave Stevenson <[email protected]>
> > + */
> > +
> > +#ifndef VC4_REGS_UNICAM_H
> > +#define VC4_REGS_UNICAM_H
> > +
> > +/*
> > + * The following values are taken from files found within the code drop
> > + * made by Broadcom for the BCM21553 Graphics Driver, predominantly in
> > + * brcm_usrlib/dag/vmcsx/vcinclude/hardware_vc4.h.
> > + * They have been modified to be only the register offset.
> > + */
> > +#define UNICAM_CTRL 0x000
> > +#define UNICAM_STA 0x004
> > +#define UNICAM_ANA 0x008
> > +#define UNICAM_PRI 0x00c
> > +#define UNICAM_CLK 0x010
> > +#define UNICAM_CLT 0x014
> > +#define UNICAM_DAT0 0x018
> > +#define UNICAM_DAT1 0x01c
> > +#define UNICAM_DAT2 0x020
> > +#define UNICAM_DAT3 0x024
> > +#define UNICAM_DLT 0x028
> > +#define UNICAM_CMP0 0x02c
> > +#define UNICAM_CMP1 0x030
> > +#define UNICAM_CAP0 0x034
> > +#define UNICAM_CAP1 0x038
> > +#define UNICAM_ICTL 0x100
> > +#define UNICAM_ISTA 0x104
> > +#define UNICAM_IDI0 0x108
> > +#define UNICAM_IPIPE 0x10c
> > +#define UNICAM_IBSA0 0x110
> > +#define UNICAM_IBEA0 0x114
> > +#define UNICAM_IBLS 0x118
> > +#define UNICAM_IBWP 0x11c
> > +#define UNICAM_IHWIN 0x120
> > +#define UNICAM_IHSTA 0x124
> > +#define UNICAM_IVWIN 0x128
> > +#define UNICAM_IVSTA 0x12c
> > +#define UNICAM_ICC 0x130
> > +#define UNICAM_ICS 0x134
> > +#define UNICAM_IDC 0x138
> > +#define UNICAM_IDPO 0x13c
> > +#define UNICAM_IDCA 0x140
> > +#define UNICAM_IDCD 0x144
> > +#define UNICAM_IDS 0x148
> > +#define UNICAM_DCS 0x200
> > +#define UNICAM_DBSA0 0x204
> > +#define UNICAM_DBEA0 0x208
> > +#define UNICAM_DBWP 0x20c
> > +#define UNICAM_DBCTL 0x300
> > +#define UNICAM_IBSA1 0x304
> > +#define UNICAM_IBEA1 0x308
> > +#define UNICAM_IDI1 0x30c
> > +#define UNICAM_DBSA1 0x310
> > +#define UNICAM_DBEA1 0x314
> > +#define UNICAM_MISC 0x400
> > +
> > +/*
> > + * The following bitmasks are from the kernel released by Broadcom
> > + * for Android - https://android.googlesource.com/kernel/bcm/
> > + * The Rhea, Hawaii, and Java chips all contain the same VideoCore4
> > + * Unicam block as BCM2835, as defined in eg
> > + * arch/arm/mach-rhea/include/mach/rdb_A0/brcm_rdb_cam.h and similar.
> > + * Values reworked to use the kernel BIT and GENMASK macros.
> > + *
> > + * Some of the bit mnenomics have been amended to match the datasheet.
> > + */
> > +/* UNICAM_CTRL Register */
> > +#define UNICAM_CPE BIT(0)
> > +#define UNICAM_MEM BIT(1)
> > +#define UNICAM_CPR BIT(2)
> > +#define UNICAM_CPM_MASK GENMASK(3, 3)
> > +#define UNICAM_CPM_CSI2 0
> > +#define UNICAM_CPM_CCP2 1
> > +#define UNICAM_SOE BIT(4)
> > +#define UNICAM_DCM_MASK GENMASK(5, 5)
> > +#define UNICAM_DCM_STROBE 0
> > +#define UNICAM_DCM_DATA 1
> > +#define UNICAM_SLS BIT(6)
> > +#define UNICAM_PFT_MASK GENMASK(11, 8)
> > +#define UNICAM_OET_MASK GENMASK(20, 12)
> > +
> > +/* UNICAM_STA Register */
> > +#define UNICAM_SYN BIT(0)
> > +#define UNICAM_CS BIT(1)
> > +#define UNICAM_SBE BIT(2)
> > +#define UNICAM_PBE BIT(3)
> > +#define UNICAM_HOE BIT(4)
> > +#define UNICAM_PLE BIT(5)
> > +#define UNICAM_SSC BIT(6)
> > +#define UNICAM_CRCE BIT(7)
> > +#define UNICAM_OES BIT(8)
> > +#define UNICAM_IFO BIT(9)
> > +#define UNICAM_OFO BIT(10)
> > +#define UNICAM_BFO BIT(11)
> > +#define UNICAM_DL BIT(12)
> > +#define UNICAM_PS BIT(13)
> > +#define UNICAM_IS BIT(14)
> > +#define UNICAM_PI0 BIT(15)
> > +#define UNICAM_PI1 BIT(16)
> > +#define UNICAM_FSI_S BIT(17)
> > +#define UNICAM_FEI_S BIT(18)
> > +#define UNICAM_LCI_S BIT(19)
> > +#define UNICAM_BUF0_RDY BIT(20)
> > +#define UNICAM_BUF0_NO BIT(21)
> > +#define UNICAM_BUF1_RDY BIT(22)
> > +#define UNICAM_BUF1_NO BIT(23)
> > +#define UNICAM_DI BIT(24)
> > +
> > +#define UNICAM_STA_MASK_ALL \
> > + (UNICAM_DL | \
> > + UNICAM_SBE | \
> > + UNICAM_PBE | \
> > + UNICAM_HOE | \
> > + UNICAM_PLE | \
> > + UNICAM_SSC | \
> > + UNICAM_CRCE | \
> > + UNICAM_IFO | \
> > + UNICAM_OFO | \
> > + UNICAM_PS | \
> > + UNICAM_PI0 | \
> > + UNICAM_PI1)
> > +
> > +/* UNICAM_ANA Register */
> > +#define UNICAM_APD BIT(0)
> > +#define UNICAM_BPD BIT(1)
> > +#define UNICAM_AR BIT(2)
> > +#define UNICAM_DDL BIT(3)
> > +#define UNICAM_CTATADJ_MASK GENMASK(7, 4)
> > +#define UNICAM_PTATADJ_MASK GENMASK(11, 8)
> > +
> > +/* UNICAM_PRI Register */
> > +#define UNICAM_PE BIT(0)
> > +#define UNICAM_PT_MASK GENMASK(2, 1)
> > +#define UNICAM_NP_MASK GENMASK(7, 4)
> > +#define UNICAM_PP_MASK GENMASK(11, 8)
> > +#define UNICAM_BS_MASK GENMASK(15, 12)
> > +#define UNICAM_BL_MASK GENMASK(17, 16)
> > +
> > +/* UNICAM_CLK Register */
> > +#define UNICAM_CLE BIT(0)
> > +#define UNICAM_CLPD BIT(1)
> > +#define UNICAM_CLLPE BIT(2)
> > +#define UNICAM_CLHSE BIT(3)
> > +#define UNICAM_CLTRE BIT(4)
> > +#define UNICAM_CLAC_MASK GENMASK(8, 5)
> > +#define UNICAM_CLSTE BIT(29)
> > +
> > +/* UNICAM_CLT Register */
> > +#define UNICAM_CLT1_MASK GENMASK(7, 0)
> > +#define UNICAM_CLT2_MASK GENMASK(15, 8)
> > +
> > +/* UNICAM_DATn Registers */
> > +#define UNICAM_DLE BIT(0)
> > +#define UNICAM_DLPD BIT(1)
> > +#define UNICAM_DLLPE BIT(2)
> > +#define UNICAM_DLHSE BIT(3)
> > +#define UNICAM_DLTRE BIT(4)
> > +#define UNICAM_DLSM BIT(5)
> > +#define UNICAM_DLFO BIT(28)
> > +#define UNICAM_DLSTE BIT(29)
> > +
> > +#define UNICAM_DAT_MASK_ALL (UNICAM_DLSTE | UNICAM_DLFO)
> > +
> > +/* UNICAM_DLT Register */
> > +#define UNICAM_DLT1_MASK GENMASK(7, 0)
> > +#define UNICAM_DLT2_MASK GENMASK(15, 8)
> > +#define UNICAM_DLT3_MASK GENMASK(23, 16)
> > +
> > +/* UNICAM_ICTL Register */
> > +#define UNICAM_FSIE BIT(0)
> > +#define UNICAM_FEIE BIT(1)
> > +#define UNICAM_IBOB BIT(2)
> > +#define UNICAM_FCM BIT(3)
> > +#define UNICAM_TFC BIT(4)
> > +#define UNICAM_LIP_MASK GENMASK(6, 5)
> > +#define UNICAM_LCIE_MASK GENMASK(28, 16)
> > +
> > +/* UNICAM_IDI0/1 Register */
> > +#define UNICAM_ID0_MASK GENMASK(7, 0)
> > +#define UNICAM_ID1_MASK GENMASK(15, 8)
> > +#define UNICAM_ID2_MASK GENMASK(23, 16)
> > +#define UNICAM_ID3_MASK GENMASK(31, 24)
> > +
> > +/* UNICAM_ISTA Register */
> > +#define UNICAM_FSI BIT(0)
> > +#define UNICAM_FEI BIT(1)
> > +#define UNICAM_LCI BIT(2)
> > +
> > +#define UNICAM_ISTA_MASK_ALL (UNICAM_FSI | UNICAM_FEI | UNICAM_LCI)
> > +
> > +/* UNICAM_IPIPE Register */
> > +#define UNICAM_PUM_MASK GENMASK(2, 0)
> > +/* Unpacking modes */
> > +#define UNICAM_PUM_NONE 0
> > +#define UNICAM_PUM_UNPACK6 1
> > +#define UNICAM_PUM_UNPACK7 2
> > +#define UNICAM_PUM_UNPACK8 3
> > +#define UNICAM_PUM_UNPACK10 4
> > +#define UNICAM_PUM_UNPACK12 5
> > +#define UNICAM_PUM_UNPACK14 6
> > +#define UNICAM_PUM_UNPACK16 7
> > +#define UNICAM_DDM_MASK GENMASK(6, 3)
> > +#define UNICAM_PPM_MASK GENMASK(9, 7)
> > +/* Packing modes */
> > +#define UNICAM_PPM_NONE 0
> > +#define UNICAM_PPM_PACK8 1
> > +#define UNICAM_PPM_PACK10 2
> > +#define UNICAM_PPM_PACK12 3
> > +#define UNICAM_PPM_PACK14 4
> > +#define UNICAM_PPM_PACK16 5
> > +#define UNICAM_DEM_MASK GENMASK(11, 10)
> > +#define UNICAM_DEBL_MASK GENMASK(14, 12)
> > +#define UNICAM_ICM_MASK GENMASK(16, 15)
> > +#define UNICAM_IDM_MASK GENMASK(17, 17)
> > +
> > +/* UNICAM_ICC Register */
> > +#define UNICAM_ICFL_MASK GENMASK(4, 0)
> > +#define UNICAM_ICFH_MASK GENMASK(9, 5)
> > +#define UNICAM_ICST_MASK GENMASK(12, 10)
> > +#define UNICAM_ICLT_MASK GENMASK(15, 13)
> > +#define UNICAM_ICLL_MASK GENMASK(31, 16)
> > +
> > +/* UNICAM_DCS Register */
> > +#define UNICAM_DIE BIT(0)
> > +#define UNICAM_DIM BIT(1)
> > +#define UNICAM_DBOB BIT(3)
> > +#define UNICAM_FDE BIT(4)
> > +#define UNICAM_LDP BIT(5)
> > +#define UNICAM_EDL_MASK GENMASK(15, 8)
> > +
> > +/* UNICAM_DBCTL Register */
> > +#define UNICAM_DBEN BIT(0)
> > +#define UNICAM_BUF0_IE BIT(1)
> > +#define UNICAM_BUF1_IE BIT(2)
> > +
> > +/* UNICAM_CMP[0,1] register */
> > +#define UNICAM_PCE BIT(31)
> > +#define UNICAM_GI BIT(9)
> > +#define UNICAM_CPH BIT(8)
> > +#define UNICAM_PCVC_MASK GENMASK(7, 6)
> > +#define UNICAM_PCDT_MASK GENMASK(5, 0)
> > +
> > +/* UNICAM_MISC register */
> > +#define UNICAM_FL0 BIT(6)
> > +#define UNICAM_FL1 BIT(9)
> > +
> > +#endif
> > diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam.c b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> > new file mode 100644
> > index 000000000000..470e691637c7
> > --- /dev/null
> > +++ b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> > @@ -0,0 +1,2570 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * BCM283x / BCM271x Unicam Capture Driver
> > + *
> > + * Copyright (C) 2017-2020 - Raspberry Pi (Trading) Ltd.
> > + *
> > + * Dave Stevenson <[email protected]>
> > + *
> > + * Based on TI am437x driver by
> > + * Benoit Parrot <[email protected]>
> > + * Lad, Prabhakar <[email protected]>
> > + *
> > + * and TI CAL camera interface driver by
> > + * Benoit Parrot <[email protected]>
> > + *
> > + *
> > + * There are two camera drivers in the kernel for BCM283x - this one
> > + * and bcm2835-camera (currently in staging).
> > + *
> > + * This driver directly controls the Unicam peripheral - there is no
> > + * involvement with the VideoCore firmware. Unicam receives CSI-2 or
> > + * CCP2 data and writes it into SDRAM.
> > + * The only potential processing options are to repack Bayer data into an
> > + * alternate format, and applying windowing.
> > + * The repacking does not shift the data, so can repack V4L2_PIX_FMT_Sxxxx10P
> > + * to V4L2_PIX_FMT_Sxxxx10, or V4L2_PIX_FMT_Sxxxx12P to V4L2_PIX_FMT_Sxxxx12,
> > + * but not generically up to V4L2_PIX_FMT_Sxxxx16. The driver will add both
> > + * formats where the relevant formats are defined, and will automatically
> > + * configure the repacking as required.
> > + * Support for windowing may be added later.
> > + *
> > + * It should be possible to connect this driver to any sensor with a
> > + * suitable output interface and V4L2 subdevice driver.
> > + *
> > + * bcm2835-camera uses the VideoCore firmware to control the sensor,
> > + * Unicam, ISP, and all tuner control loops. Fully processed frames are
> > + * delivered to the driver by the firmware. It only has sensor drivers
> > + * for Omnivision OV5647, and Sony IMX219 sensors.
> > + *
> > + * The two drivers are mutually exclusive for the same Unicam instance.
> > + * The VideoCore firmware checks the device tree configuration during boot.
> > + * If it finds device tree nodes called csi0 or csi1 it will block the
> > + * firmware from accessing the peripheral, and bcm2835-camera will
> > + * not be able to stream data.
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/delay.h>
> > +#include <linux/device.h>
> > +#include <linux/dma-mapping.h>
> > +#include <linux/err.h>
> > +#include <linux/init.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/io.h>
> > +#include <linux/module.h>
> > +#include <linux/of_device.h>
> > +#include <linux/of_graph.h>
> > +#include <linux/pinctrl/consumer.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pm_runtime.h>
> > +#include <linux/slab.h>
> > +#include <linux/uaccess.h>
> > +#include <linux/videodev2.h>
> > +
> > +#include <media/v4l2-async.h>
> > +#include <media/v4l2-common.h>
> > +#include <media/v4l2-ctrls.h>
> > +#include <media/v4l2-dev.h>
> > +#include <media/v4l2-device.h>
> > +#include <media/v4l2-dv-timings.h>
> > +#include <media/v4l2-event.h>
> > +#include <media/v4l2-ioctl.h>
> > +#include <media/v4l2-fwnode.h>
> > +#include <media/v4l2-mc.h>
> > +#include <media/videobuf2-dma-contig.h>
> > +
> > +#include "bcm2835-unicam-regs.h"
> > +
> > +#define UNICAM_MODULE_NAME "unicam"
> > +
> ...
> > +
> > +static int unicam_sd_enable_streams(struct v4l2_subdev *sd, u32 pad,
> > + u64 streams_mask)
> > +{
> > + struct unicam_device *unicam = sd_to_unicam_device(sd);
> > + struct v4l2_subdev_state *state;
> > + u32 other_pad, other_stream;
> > + int ret;
> > +
> > + if (WARN_ON(streams_mask != 1))
> > + return -EINVAL;
>
> Can we print some error message additionally to the stacktrace?

I would actually drop this check. The .enable_streams() and
.disable_streams() operations are supposed to be called using new helper
functions that will be included in the v11 of the V4L2 streams series,
the streams mask check should be moved there.

> > +
> > + state = v4l2_subdev_lock_active_state(sd);
> > +
> > + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
> > + &other_pad, &other_stream);
> > +
> > + v4l2_subdev_unlock_state(state);
> > +
> > + if (ret)
> > + return ret;
> > +
> > + unicam->sequence = 0;
> > +
> > + dev_dbg(unicam->dev, "Running with %u data lanes\n",
> > + unicam->active_data_lanes);
> > +
> > + ret = clk_set_min_rate(unicam->vpu_clock, UNICAM_MIN_VPU_CLOCK_RATE);
> > + if (ret) {
> > + dev_err(unicam->dev, "failed to set up VPU clock\n");
> > + return ret;
> > + }
> > +
> > + ret = clk_prepare_enable(unicam->vpu_clock);
> > + if (ret) {
> > + dev_err(unicam->dev, "Failed to enable VPU clock: %d\n", ret);
> > + goto err_vpu_clock;
> > + }
> > +
> > + ret = clk_set_rate(unicam->clock, 100 * 1000 * 1000);
> > + if (ret) {
> > + dev_err(unicam->dev, "failed to set up CSI clock\n");
> > + goto err_vpu_prepare;
> > + }
> > +
> > + ret = clk_prepare_enable(unicam->clock);
> > + if (ret) {
> > + dev_err(unicam->dev, "Failed to enable CSI clock: %d\n", ret);
> > + goto err_vpu_prepare;
> > + }
> > +
> > + ret = v4l2_subdev_call(unicam->sensor.subdev, video, enable_streams,
> > + unicam->sensor.pad->index, BIT(other_stream));
> > +
> > + if (ret && ret == -ENOIOCTLCMD)
> > + ret = v4l2_subdev_call(unicam->sensor.subdev, video, s_stream, 1);
> > +
> > + if (ret) {
> > + dev_err(unicam->dev, "stream on failed in subdev\n");
> > + goto err_enable_stream;
> > + }
> > +
> > + unicam->subdev.streaming = true;
> > +
> > + return 0;
> > +
> > +err_enable_stream:
> > + clk_disable_unprepare(unicam->clock);
> > +err_vpu_prepare:
> > + clk_disable_unprepare(unicam->vpu_clock);
> > +err_vpu_clock:
> > + if (clk_set_min_rate(unicam->vpu_clock, 0))
> > + dev_err(unicam->dev, "failed to reset the VPU clock\n");
> > + return ret;
> > +}
> > +
> > +static int unicam_sd_disable_streams(struct v4l2_subdev *sd, u32 pad,
> > + u64 streams_mask)
> > +{
> > + struct unicam_device *unicam = sd_to_unicam_device(sd);
> > + struct v4l2_subdev_state *state;
> > + u32 other_pad, other_stream;
> > + int ret;
> > +
> > + if (WARN_ON(streams_mask != 1))
> > + return -EINVAL;
> Can we print some error message additionally to the stacktrace?
> > +
> > + state = v4l2_subdev_lock_active_state(sd);
> > +
> > + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
> > + &other_pad, &other_stream);
> > +
> > + v4l2_subdev_unlock_state(state);
> > +
> > + if (ret) {
> > + dev_err(unicam->dev, "disable streams failed: %d\n", ret);
> > + return ret;
> > + }
> > +
> > + ret = v4l2_subdev_call(unicam->sensor.subdev, video, disable_streams,
> > + unicam->sensor.pad->index, BIT(other_stream));
> > +
> > + if (ret && ret == -ENOIOCTLCMD) {
> > + /* The sensor does not support disable streams. */
> > + if (unicam->node[UNICAM_IMAGE_NODE].streaming &&
> > + other_pad == unicam->node[UNICAM_METADATA_NODE].pad.index) {
> > + /* We won't stop the sensor yet. */
> > + unicam->subdev.streaming = false;
> > + return 0;
> > + }
> > + /* There is no more the metadata node, we can stop. */
> > + ret = v4l2_subdev_call(unicam->sensor.subdev, video, s_stream, 0);
> > + }
> > +
> > + clk_disable_unprepare(unicam->clock);
> > + if (clk_set_min_rate(unicam->vpu_clock, 0))
> > + dev_err(unicam->dev, "failed to reset the VPU clock\n");
> > + clk_disable_unprepare(unicam->vpu_clock);
> > +
> > + unicam->subdev.streaming = false;
> > +
> > + return 0;
> > +}
> > +
> > +static int unicam_subdev_set_pad_format(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + struct v4l2_subdev_format *format)
> > +{
> > + struct unicam_device *unicam = sd_to_unicam_device(sd);
> > + struct v4l2_mbus_framefmt *sink_format, *source_format;
> > + const struct unicam_fmt *fmtinfo;
> > + int ret = 0;
> > +
> > + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && unicam->subdev.streaming)
> > + return -EBUSY;
> > +
> > + /* No transcoding, source and sink formats must match. */
> > + if (unicam_sd_pad_is_source(format->pad))
> > + return v4l2_subdev_get_fmt(sd, state, format);
> > +
> > + fmtinfo = unicam_find_format_by_code(format->format.code, format->pad);
> > + if (!fmtinfo)
> > + fmtinfo = &unicam_image_formats[0];
> > +
> > + if (format->pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> > + format->format.width = clamp_t(unsigned int,
> > + format->format.width,
> > + UNICAM_MIN_WIDTH,
> > + UNICAM_MAX_WIDTH);
> > + format->format.height = clamp_t(unsigned int,
> > + format->format.height,
> > + UNICAM_MIN_HEIGHT,
> > + UNICAM_MAX_HEIGHT);
> > + }
> > + format->format.field = V4L2_FIELD_NONE;
> > +
> > + v4l2_subdev_lock_state(state);
> > +
> > + sink_format = v4l2_state_get_stream_format(state, format->pad,
> > + format->stream);
> > + source_format = v4l2_subdev_state_get_opposite_stream_format(state,
> > + format->pad,
> > + format->stream);
> > + if (!sink_format || !source_format) {
> > + ret = -EINVAL;
> > + goto out;
> > + }
> > +
> > + *sink_format = format->format;
> > + *source_format = format->format;
> > +out:
> > + v4l2_subdev_unlock_state(state);
> > +
> > + return ret;
> > +}
> > +
> > +static int unicam_subdev_enum_frame_size(struct v4l2_subdev *sd,
> > + struct v4l2_subdev_state *state,
> > + struct v4l2_subdev_frame_size_enum *fse)
> > +{
> > + const struct unicam_fmt *fmtinfo;
> > + u32 pad, stream;
> > + int ret = -EINVAL;
> > +
> > + if (fse->index > 0)
> > + return ret;
> > +
> > + v4l2_subdev_lock_state(state);
> > +
> > + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, fse->pad,
> > + fse->stream, &pad,
> > + &stream);
> > + if (ret)
> > + goto out;
> > +
> > + if (unicam_sd_pad_is_source(fse->pad)) {
> > + /* No transcoding, source and sink formats must match. */
> > + struct v4l2_mbus_framefmt *fmt;
> > +
> > + fmt = v4l2_state_get_stream_format(state, pad, stream);
> > + if (!fmt)
> > + goto out;
> > +
> > + if (fse->code != fmt->code)
> > + goto out;
> > +
> > + fse->min_width = fmt->width;
> > + fse->max_width = fmt->width;
> > + fse->min_height = fmt->height;
> > + fse->max_height = fmt->height;
> > + } else {
> > + fmtinfo = unicam_find_format_by_code(fse->code, pad);
> > + if (!fmtinfo)
> > + goto out;
> > +
> > + fse->min_width = UNICAM_MIN_WIDTH;
> > + fse->max_width = UNICAM_MAX_WIDTH;
> > + fse->min_height = UNICAM_MIN_HEIGHT;
> > + fse->max_height = UNICAM_MAX_HEIGHT;
> > + }
> > +
> > +out:
> > + v4l2_subdev_unlock_state(state);
> > +
> > + return ret;
> > +}
> > +
> > +static const struct v4l2_subdev_video_ops unicam_subdev_video_ops = {
> > + .enable_streams = unicam_sd_enable_streams,
> > + .disable_streams = unicam_sd_disable_streams,
> > +};
> > +
> > +static const struct v4l2_subdev_pad_ops unicam_subdev_pad_ops = {
> > + .init_cfg = unicam_subdev_init_cfg,
> > + .enum_mbus_code = unicam_subdev_enum_mbus_code,
> > + .get_fmt = v4l2_subdev_get_fmt,
> > + .set_fmt = unicam_subdev_set_pad_format,
> > + .set_routing = unicam_subdev_set_routing,
> > + .enum_frame_size = unicam_subdev_enum_frame_size,
> > +};
> > +
> > +static const struct v4l2_subdev_ops unicam_subdev_ops = {
> > + .video = &unicam_subdev_video_ops,
> > + .pad = &unicam_subdev_pad_ops,
> > +};
> > +
> > +static const struct media_entity_operations unicam_subdev_media_ops = {
> > + .link_validate = v4l2_subdev_link_validate,
> > + .has_route = v4l2_subdev_has_route,
> > +};
> > +
> > +static int unicam_init_and_register_subdev(struct unicam_device *unicam)
> > +{
> > + struct v4l2_subdev *sd = &unicam->subdev.sd;
> > + int ret;
> > +
> > + v4l2_subdev_init(sd, &unicam_subdev_ops);
> > + v4l2_set_subdevdata(sd, unicam);
> > + unicam->subdev.sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> > + unicam->subdev.sd.entity.ops = &unicam_subdev_media_ops;
> > + unicam->subdev.sd.dev = unicam->dev;
> > + unicam->subdev.sd.owner = THIS_MODULE;
> > + unicam->subdev.sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_MULTIPLEXED;
> > + snprintf(unicam->subdev.sd.name, sizeof(unicam->subdev.sd.name), "unicam-subdev");
> > +
> > + unicam->subdev.pads[UNICAM_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> > +
> > + unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_IMAGE].flags = MEDIA_PAD_FL_SOURCE;
> > + unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_METADATA].flags = MEDIA_PAD_FL_SOURCE;
> > +
> > + ret = media_entity_pads_init(&sd->entity,
> > + ARRAY_SIZE(unicam->subdev.pads), unicam->subdev.pads);
> > + if (ret) {
> > + dev_err(unicam->dev, "Could not init media entity");
> > + return ret;
> > + }
> > +
> > + ret = v4l2_subdev_init_finalize(sd);
> > + if (ret) {
> > + dev_err(unicam->dev, "Could not finalize init for subdev");
> > + return ret;
> > + }
> > +
> > + return v4l2_device_register_subdev(&unicam->v4l2_dev, sd);
> > +}
> > +
> > +/* -----------------------------------------------------------------------------
> > + * Videobuf2 Queue Operations
> > + */
> > +
> > +static int unicam_queue_setup(struct vb2_queue *vq,
> > + unsigned int *nbuffers,
> > + unsigned int *nplanes,
> > + unsigned int sizes[],
> > + struct device *alloc_devs[])
> > +{
> > + struct unicam_node *node = vb2_get_drv_priv(vq);
> > + struct unicam_device *unicam = node->dev;
> > + unsigned int size = is_image_node(node) ?
> > + node->v_fmt.fmt.pix.sizeimage :
> > + node->v_fmt.fmt.meta.buffersize;
> > +
> > + if (vq->num_buffers + *nbuffers < 3)
> > + *nbuffers = 3 - vq->num_buffers;
> > +
> > + if (*nplanes) {
> > + if (sizes[0] < size) {
> > + dev_dbg(unicam->dev, "sizes[0] %i < size %u\n", sizes[0],
> > + size);
> > + return -EINVAL;
> > + }
> > + size = sizes[0];
> > + }
> > +
> > + *nplanes = 1;
> > + sizes[0] = size;
> > +
> > + return 0;
> > +}
> > +
> > +static int unicam_buffer_prepare(struct vb2_buffer *vb)
> > +{
> > + struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
> > + struct unicam_device *unicam = node->dev;
> > + struct unicam_buffer *buf = to_unicam_buffer(vb);
> > + unsigned long size;
> > +
> > + if (WARN_ON(!node->fmt))
> > + return -EINVAL;
> Can we print some error message additionally to the stacktrace?
> > +
> > + size = is_image_node(node) ? node->v_fmt.fmt.pix.sizeimage :
> > + node->v_fmt.fmt.meta.buffersize;
> > + if (vb2_plane_size(vb, 0) < size) {
> > + dev_dbg(unicam->dev, "data will not fit into plane (%lu < %lu)\n",
> > + vb2_plane_size(vb, 0), size);
> > + return -EINVAL;
> > + }
> > +
> > + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
> > + return 0;
> > +}
> > +
> ...
> > +
> > +static int unicam_connect_of_subdevs(struct unicam_device *unicam)
> > +{
> > + struct v4l2_fwnode_endpoint ep = { };
> > + struct fwnode_handle *ep_handle;
> > + struct v4l2_async_subdev *asd;
> > + unsigned int lane;
> > + int ret = -EINVAL;
> > +
> > + if (of_property_read_u32(unicam->dev->of_node, "brcm,num-data-lanes",
> > + &unicam->max_data_lanes) < 0) {
> > + dev_err(unicam->dev, "DT property %s not set\n",
> > + "brcm,num-data-lanes");
> > + return -EINVAL;
> > + }
> > +
> > + /* Get the local endpoint and remote device. */
> > + ep_handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(unicam->dev),
> > + 0, 0,
> > + FWNODE_GRAPH_ENDPOINT_NEXT);
> > + if (!ep_handle) {
> > + dev_err(unicam->dev, "No endpoint\n");
> > + return -ENODEV;
> > + }
> > +
> > + /* Parse the local endpoint and validate its configuration. */
> > + if (v4l2_fwnode_endpoint_alloc_parse(ep_handle, &ep)) {
> > + dev_err(unicam->dev, "could not parse endpoint\n");
> > + goto cleanup_exit;
> > + }
> > +
> > + dev_dbg(unicam->dev, "parsed local endpoint, bus_type %u\n",
> > + ep.bus_type);
> > +
> > + unicam->bus_type = ep.bus_type;
> > +
> > + switch (ep.bus_type) {
> > + case V4L2_MBUS_CSI2_DPHY:
> > + switch (ep.bus.mipi_csi2.num_data_lanes) {
> > + case 1:
> > + case 2:
> > + case 4:
> > + break;
> > +
> > + default:
> > + dev_err(unicam->dev, "%u data lanes not supported\n",
> > + ep.bus.mipi_csi2.num_data_lanes);
> > + goto cleanup_exit;
> > + }
> > +
> > + for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
> > + if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
> > + dev_err(unicam->dev, "data lanes reordering not supported\n");
> > + goto cleanup_exit;
> > + }
> > + }
> > +
> > + if (ep.bus.mipi_csi2.num_data_lanes > unicam->max_data_lanes) {
> > + dev_err(unicam->dev, "endpoint requires %u data lanes when %u are supported\n",
> > + ep.bus.mipi_csi2.num_data_lanes,
> > + unicam->max_data_lanes);
> > + }
> > +
> > + unicam->active_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
> > + unicam->bus_flags = ep.bus.mipi_csi2.flags;
> > +
> > + break;
> > +
> > + case V4L2_MBUS_CCP2:
> > + if (ep.bus.mipi_csi1.clock_lane != 0 ||
> > + ep.bus.mipi_csi1.data_lane != 1) {
> > + dev_err(unicam->dev, "unsupported lanes configuration\n");
> > + goto cleanup_exit;
> > + }
> > +
> > + unicam->max_data_lanes = 1;
> > + unicam->active_data_lanes = 1;
> > + unicam->bus_flags = ep.bus.mipi_csi1.strobe;
> > + break;
> > +
> > + default:
> > + /* Unsupported bus type */
> > + dev_err(unicam->dev, "unsupported bus type %u\n",
> > + ep.bus_type);
> > + goto cleanup_exit;
> > + }
> > +
> > + dev_dbg(unicam->dev, "%s bus, %u data lanes, flags=0x%08x\n",
> > + unicam->bus_type == V4L2_MBUS_CSI2_DPHY ? "CSI-2" : "CCP2",
> > + unicam->active_data_lanes, unicam->bus_flags);
> > +
> > + /* Initialize and register the async notifier. */
> > + v4l2_async_nf_init(&unicam->notifier);
> > +
> > + asd = v4l2_async_nf_add_fwnode_remote(&unicam->notifier, ep_handle,
> > + struct v4l2_async_subdev);
> > +
> > + fwnode_handle_put(ep_handle);
> > + ep_handle = NULL;
> > +
> > + if (IS_ERR(asd)) {
> please provide a log entry here
> > + ret = PTR_ERR(asd);
> > + goto cleanup_exit;
> > + }
> > +
> > + unicam->notifier.ops = &unicam_async_ops;
> > +
> > + ret = v4l2_async_nf_register(&unicam->v4l2_dev, &unicam->notifier);
> > + if (ret) {
> > + dev_err(unicam->dev, "Error registering device notifier: %d\n", ret);
> > + goto cleanup_exit;
> > + }
> > +
> > + return 0;
> > +
> > +cleanup_exit:
> > + v4l2_fwnode_endpoint_free(&ep);
> > + fwnode_handle_put(ep_handle);
> > +
> > + return ret;
> > +}
> > +
> > +static int unicam_media_dev_init(struct unicam_device *unicam)
> > +{
> > + int ret = 0;
> no need to init ret here
> > +
> > + unicam->mdev.dev = unicam->dev;
> > + strscpy(unicam->mdev.model, UNICAM_MODULE_NAME,
> > + sizeof(unicam->mdev.model));
> > + strscpy(unicam->mdev.serial, "", sizeof(unicam->mdev.serial));
> > + snprintf(unicam->mdev.bus_info, sizeof(unicam->mdev.bus_info),
> > + "platform:%s", dev_name(unicam->dev));
> > + unicam->mdev.hw_revision = 0;
> > +
> > + media_device_init(&unicam->mdev);
> > +
> > + unicam->v4l2_dev.mdev = &unicam->mdev;
> > +
> > + ret = v4l2_device_register(unicam->dev, &unicam->v4l2_dev);
> > + if (ret < 0) {
> > + dev_err(unicam->dev,
> > + "Unable to register v4l2 device\n");
> > + return ret;
> > + }
> > +
> > + ret = media_device_register(&unicam->mdev);
> > + if (ret < 0) {
> > + dev_err(unicam->dev,
> > + "Unable to register media-controller device\n");
> > + goto err_v4l2_unregister;
> > + }
> > +
> > + return 0;
> > +
> > +err_v4l2_unregister:
> > + v4l2_device_unregister(&unicam->v4l2_dev);
> > + return ret;
> > +}
> > +
> > +static int unicam_probe(struct platform_device *pdev)
> > +{
> > + struct unicam_device *unicam;
> > + int ret = 0;
> > +
> > + unicam = kzalloc(sizeof(*unicam), GFP_KERNEL);
> > + if (!unicam)
> > + return -ENOMEM;
> > +
> > + kref_init(&unicam->kref);
> > + unicam->dev = &pdev->dev;
> > + /* set the driver data in platform device */
> > + platform_set_drvdata(pdev, unicam);
> > +
> > + unicam->base = devm_platform_ioremap_resource_byname(pdev, "unicam");
> > + if (IS_ERR(unicam->base)) {
> > + ret = PTR_ERR(unicam->base);
> > + goto err_unicam_put;
> > + }
> > +
> > + unicam->clk_gate_base = devm_platform_ioremap_resource_byname(pdev, "cmi");
> > + if (IS_ERR(unicam->clk_gate_base)) {
> > + ret = PTR_ERR(unicam->clk_gate_base);
> > + goto err_unicam_put;
> > + }
> > +
> > + unicam->clock = devm_clk_get(&pdev->dev, "lp");
> > + if (IS_ERR(unicam->clock)) {
> > + dev_err(unicam->dev, "Failed to get lp clock\n");
> > + ret = PTR_ERR(unicam->clock);
> > + goto err_unicam_put;
> > + }
> > +
> > + unicam->vpu_clock = devm_clk_get(&pdev->dev, "vpu");
> > + if (IS_ERR(unicam->vpu_clock)) {
> > + dev_err(unicam->dev, "Failed to get vpu clock\n");
> > + ret = PTR_ERR(unicam->vpu_clock);
> > + goto err_unicam_put;
> > + }
> > +
> > + ret = platform_get_irq(pdev, 0);
> > + if (ret <= 0) {
> > + dev_err(&pdev->dev, "No IRQ resource\n");
> > + ret = -EINVAL;
> > + goto err_unicam_put;
> > + }
> > +
> > + ret = devm_request_irq(&pdev->dev, ret, unicam_isr, 0,
> > + "unicam_capture0", unicam);
> > + if (ret) {
> > + dev_err(&pdev->dev, "Unable to request interrupt\n");
> > + ret = -EINVAL;
> > + goto err_unicam_put;
> > + }
> > +
> > + ret = unicam_media_dev_init(unicam);
> > + if (ret) {
> > + dev_err(unicam->dev,
> > + "Unable to initialize media device\n");
> > + goto err_unicam_put;
> > + }
> > +
> > + ret = unicam_init_and_register_subdev(unicam);
> > + if (ret) {
> > + dev_err(&pdev->dev, "Failed to register internal subdev\n");
>
> please drop this unnecessary log entry, because the called function
> already provide more helpful log entries.
>
> Best regards
>
> > + goto err_media_unregister;
> > + }
> > +
> > + ret = unicam_connect_of_subdevs(unicam);
> > + if (ret) {
> > + dev_err(&pdev->dev, "Failed to connect subdevs\n");
> > + goto err_subdev_unregister;
> > + }
> > +
> > + /* Enable the block power domain */
> > + pm_runtime_enable(&pdev->dev);
> > +
> > + return 0;
> > +
> > +err_subdev_unregister:
> > + v4l2_subdev_cleanup(&unicam->subdev.sd);
> > +err_media_unregister:
> > + media_device_unregister(&unicam->mdev);
> > +err_unicam_put:
> > + unicam_put(unicam);
> > +
> > + return ret;
> > +}
> > +
> > +static int unicam_remove(struct platform_device *pdev)
> > +{
> > + struct unicam_device *unicam = platform_get_drvdata(pdev);
> > +
> > + unicam_unregister_nodes(unicam);
> > + v4l2_device_unregister(&unicam->v4l2_dev);
> > + media_device_unregister(&unicam->mdev);
> > + v4l2_async_nf_unregister(&unicam->notifier);
> > + unicam_put(unicam);
> > +
> > + pm_runtime_disable(&pdev->dev);
> > +
> > + return 0;
> > +}
> > +
> > +static const struct of_device_id unicam_of_match[] = {
> > + { .compatible = "brcm,bcm2835-unicam", },
> > + { /* sentinel */ },
> > +};
> > +MODULE_DEVICE_TABLE(of, unicam_of_match);
> > +
> > +static struct platform_driver unicam_driver = {
> > + .probe = unicam_probe,
> > + .remove = unicam_remove,
> > + .driver = {
> > + .name = UNICAM_MODULE_NAME,
> > + .of_match_table = of_match_ptr(unicam_of_match),
> > + },
> > +};
> > +
> > +module_platform_driver(unicam_driver);
> > +
> > +MODULE_AUTHOR("Dave Stevenson <[email protected]>");
> > +MODULE_DESCRIPTION("BCM2835 Unicam driver");
> > +MODULE_LICENSE("GPL");

--
Regards,

Laurent Pinchart

2022-02-20 22:26:18

by Jean-Michel Hautbois

[permalink] [raw]
Subject: Re: [PATCH v5 00/11] Add support for BCM2835 camera interface (unicam)

Hi Stefan,

On 16/02/2022 21:57, Stefan Wahren wrote:
> Hi Jean-Michel,
>
> Am 08.02.22 um 16:50 schrieb Jean-Michel Hautbois:
>> Hello !
>>
>> ...
>>
>> In order to properly configure the media pipeline, it is needed to call
>> the usual ioctls, and configure routing in order to send the embedded
>> data from the sensor to the "unicam-embedded" device node :
>>
>> ```
>> media=0
>> media-ctl -d${media} -l "'imx219 2-0010':0->'unicam-subdev':0 [1]"
>> media-ctl -d${media} -l "'unicam-subdev':1->'unicam-image':0 [1]"
>> media-ctl -d${media} -v -R "'unicam-subdev' [0/0->1/0[1],0/1->2/0[1]]"
>> media-ctl -d${media} -V "'imx219 2-0010':0/0 [fmt:SRGGB10_1X10/3280x2464 field:none]"
>> v4l2-ctl -d0 --set-fmt-video width=3280,height=2464,pixelformat='pRAA',field=none
>> media-ctl -d${media} -v -V "'imx219 2-0010':0/1 [fmt:METADATA_8/16384x1 field:none]"
>> media-ctl -d${media} -p
>> ```
>
> i tried to test the unicam driver on a Raspberry Pi 4 with the imx219
> camera (based on 5.17-rc4). The unicam & imx219 driver probes and
> /dev/video0 is created.
>
> If a execute the first media-ctl command, it complains with invalid
> argument 22. Is there a more fool-proof variant to configure this (a
> script or something else)? I never used the unicam driver before.
>
> Here is the output of
>
> $ mediactl -d0 -p
>

Based on your output, I suppose the issue is the naming of the imx219
media entity ('imx219 2-0010' vs 'imx219 5-0010').
You could add a '-v' in the line to help you I suppose.

A more bullet-proof version of the commands is certainly doable, not
sure how though as I would not like to rewrite a libcamera-like command ;-).

> Media controller API version 5.17.0
>
> Media device information
> ------------------------
> driver          unicam
> model           unicam
> serial
> bus info        platform:fe801000.csi
> hw revision     0x0
> driver version  5.17.0
>
> Device topology
> - entity 1: unicam-subdev (3 pads, 3 links, 2 routes)
>             type V4L2 subdev subtype Unknown flags 0
>             device node name /dev/v4l-subdev0
>     routes:
>         0/0 -> 1/0 [ACTIVE]
>         0/1 -> 2/0 [ACTIVE]
>     pad0: Sink
>         [stream:0 fmt:unknown/0x0]
>         [stream:1 fmt:unknown/0x0]
>         <- "imx219 5-0010":0 [ENABLED,IMMUTABLE]
>     pad1: Source
>         [stream:0 fmt:unknown/0x0]
>         -> "unicam-image":0 [ENABLED,IMMUTABLE]
>     pad2: Source
>         [stream:0 fmt:unknown/0x0]
>         -> "unicam-embedded":0 [ENABLED,IMMUTABLE]
>
> - entity 5: imx219 5-0010 (1 pad, 1 link, 2 routes)
>             type V4L2 subdev subtype Sensor flags 0
>             device node name /dev/v4l-subdev1
>     routes:
>         0/0 -> 0/0 [ACTIVE, IMMUTABLE, SOURCE]
>         0/0 -> 0/1 [ACTIVE, SOURCE]
>     pad0: Source
>         [stream:0 fmt:SRGGB10_1X10/3280x2464 field:none colorspace:raw
>          crop.bounds:(8,8)/3280x2464
>          crop:(8,8)/3280x2464]
>         [stream:1 fmt:METADATA_8/16384x1 field:none
>          crop.bounds:(8,8)/3280x2464
>          crop:(8,8)/3280x2464]
>         -> "unicam-subdev":0 [ENABLED,IMMUTABLE]
>
> - entity 9: unicam-image (1 pad, 1 link, 0 route)
>             type Node subtype V4L flags 1
>             device node name /dev/video0
>     pad0: Sink
>         <- "unicam-subdev":1 [ENABLED,IMMUTABLE]
>
> - entity 15: unicam-embedded (1 pad, 1 link, 0 route)
>              type Node subtype V4L flags 0
>              device node name /dev/video1
>     pad0: Sink
>         <- "unicam-subdev":2 [ENABLED,IMMUTABLE]
>

2022-02-21 08:59:47

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v5 09/11] media: imx219: Introduce the set_routing operation

Hi Jean-Michel,

Thank you for the patch.

On Tue, Feb 08, 2022 at 04:50:25PM +0100, Jean-Michel Hautbois wrote:
> As we want to use multiplexed streams API, we need to be able to set the
> pad routing. Introduce the set_routing operation.
>
> As this operation is required for a multiplexed able sensor, add the
> V4L2_SUBDEV_FL_MULTIPLEXED flag.
>
> Signed-off-by: Jean-Michel Hautbois <[email protected]>
> ---
> drivers/media/i2c/imx219.c | 82 ++++++++++++++++++++++++++++++++++++--
> 1 file changed, 79 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c
> index abcaee15c4a0..35b61fad8e35 100644
> --- a/drivers/media/i2c/imx219.c
> +++ b/drivers/media/i2c/imx219.c
> @@ -118,6 +118,10 @@
> #define IMX219_PIXEL_ARRAY_WIDTH 3280U
> #define IMX219_PIXEL_ARRAY_HEIGHT 2464U
>
> +/* Embedded metadata stream structure */
> +#define IMX219_EMBEDDED_LINE_WIDTH 16384
> +#define IMX219_NUM_EMBEDDED_LINES 1
> +
> struct imx219_reg {
> u16 address;
> u8 val;
> @@ -784,15 +788,85 @@ static void imx219_init_formats(struct v4l2_subdev_state *state)
> format->height = supported_modes[0].height;
> format->field = V4L2_FIELD_NONE;
> format->colorspace = V4L2_COLORSPACE_RAW;
> +
> + if (state->routing.routes[1].flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE) {

There's no guarantee the routing table will have 2 entries.

> + format = v4l2_state_get_stream_format(state, 0, 1);
> + format->code = MEDIA_BUS_FMT_METADATA_8;
> + format->width = IMX219_EMBEDDED_LINE_WIDTH;
> + format->height = 1;
> + format->field = V4L2_FIELD_NONE;
> + format->colorspace = V4L2_COLORSPACE_DEFAULT;
> + }
> }
>
> -static int imx219_init_cfg(struct v4l2_subdev *sd,
> - struct v4l2_subdev_state *state)
> +static int __imx219_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state)
> {
> + struct v4l2_subdev_route routes[] = {
> + {
> + .source_pad = 0,
> + .source_stream = 0,
> + .flags = V4L2_SUBDEV_ROUTE_FL_IMMUTABLE |
> + V4L2_SUBDEV_ROUTE_FL_SOURCE |
> + V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> + },
> + {

}, {

> + .source_pad = 0,
> + .source_stream = 1,
> + .flags = V4L2_SUBDEV_ROUTE_FL_SOURCE |
> + V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> + }
> + };

That doesn't look right. You're hardcoding the routes, disregarding
completely what userspace wants to program. This default configuration
must move to .init_cfg().

> +
> + struct v4l2_subdev_krouting routing = {
> + .num_routes = ARRAY_SIZE(routes),
> + .routes = routes,
> + };
> +
> + int ret;
> +
> + ret = v4l2_subdev_set_routing(sd, state, &routing);
> + if (ret)
> + return ret;
> +
> imx219_init_formats(state);
> +
> return 0;
> }
>
> +static int imx219_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + enum v4l2_subdev_format_whence which,
> + struct v4l2_subdev_krouting *routing)
> +{
> + int ret;
> +
> + if (routing->num_routes == 0 || routing->num_routes > 2)
> + return -EINVAL;
> +
> + v4l2_subdev_lock_state(state);

Note for the future: v11 of the V4L2 streams series will handle locking
in the code, so you'll have to drop this (same in .init_cfg()).

> +
> + ret = __imx219_set_routing(sd, state);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int imx219_init_cfg(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state)
> +{
> + int ret;
> +
> + v4l2_subdev_lock_state(state);
> +
> + ret = __imx219_set_routing(sd, state);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> static int imx219_enum_mbus_code(struct v4l2_subdev *sd,
> struct v4l2_subdev_state *sd_state,
> struct v4l2_subdev_mbus_code_enum *code)
> @@ -1251,6 +1325,7 @@ static const struct v4l2_subdev_pad_ops imx219_pad_ops = {
> .get_fmt = imx219_get_pad_format,
> .set_fmt = imx219_set_pad_format,
> .get_selection = imx219_get_selection,
> + .set_routing = imx219_set_routing,
> .enum_frame_size = imx219_enum_frame_size,
> };
>
> @@ -1509,7 +1584,8 @@ static int imx219_probe(struct i2c_client *client)
>
> /* Initialize subdev */
> imx219->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
> - V4L2_SUBDEV_FL_HAS_EVENTS;
> + V4L2_SUBDEV_FL_HAS_EVENTS |
> + V4L2_SUBDEV_FL_MULTIPLEXED;
> imx219->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
>
> /* Initialize source pad */

--
Regards,

Laurent Pinchart

2022-02-21 09:13:48

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v5 11/11] media: imx219: Add support for the V4L2 subdev active state

Hi Jean-Michel,

Thank you for the patch.

On Tue, Feb 08, 2022 at 04:50:27PM +0100, Jean-Michel Hautbois wrote:
> Now that we have multiplexed streams support in the V4L2 API, add
> support for the subdev active state in the sensor.
>
> In order to use state to manage the active configuration, we need to
> initialize the subdevice state with a call to
> v4l2_subdev_init_finalize() before registering it. The call to
> v4l2_subdev_cleanup() is also required to release the resources before
> unregistering.
>
> Accessing the configuration is then done after a call to
> v4l2_subdev_lock_state() and the set_fmt operation can then call
> v4l2_state_get_stream_format() directly.
>
> The get_fmt operation does not need to be complex, and a simple call to
> v4l2_subdev_get_fmt will do the trick, as it will grab the configured
> format based on the state.
>
> Signed-off-by: Jean-Michel Hautbois <[email protected]>
> ---
> drivers/media/i2c/imx219.c | 151 +++++++++++++++++++------------------
> 1 file changed, 77 insertions(+), 74 deletions(-)
>
> diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c
> index f005ad8d2124..f6322f39c3f1 100644
> --- a/drivers/media/i2c/imx219.c
> +++ b/drivers/media/i2c/imx219.c
> @@ -887,78 +887,43 @@ static int imx219_enum_frame_size(struct v4l2_subdev *sd,
> struct v4l2_subdev_state *sd_state,
> struct v4l2_subdev_frame_size_enum *fse)
> {
> - struct imx219 *imx219 = to_imx219(sd);
> - u32 code;
> -
> - if (fse->index >= ARRAY_SIZE(supported_modes))
> - return -EINVAL;
> -
> - mutex_lock(&imx219->mutex);
> - code = imx219_get_format_code(imx219, fse->code);
> - mutex_unlock(&imx219->mutex);
> - if (fse->code != code)
> - return -EINVAL;
> -
> - fse->min_width = supported_modes[fse->index].width;
> - fse->max_width = fse->min_width;
> - fse->min_height = supported_modes[fse->index].height;
> - fse->max_height = fse->min_height;
> + unsigned int i;
>
> - return 0;
> -}
> + if (fse->stream == 0) {
> + for (i = 0; i < ARRAY_SIZE(imx219_mbus_formats); ++i) {
> + if (imx219_mbus_formats[i] == fse->code)
> + break;
> + }
>
> -static void imx219_reset_colorspace(struct v4l2_mbus_framefmt *fmt)
> -{
> - fmt->colorspace = V4L2_COLORSPACE_SRGB;
> - fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
> - fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
> - fmt->colorspace,
> - fmt->ycbcr_enc);
> - fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
> -}
> + if (i == ARRAY_SIZE(imx219_mbus_formats))
> + return -EINVAL;
>
> -static void imx219_update_pad_format(struct imx219 *imx219,
> - const struct imx219_mode *mode,
> - struct v4l2_subdev_format *fmt)
> -{
> - fmt->format.width = mode->width;
> - fmt->format.height = mode->height;
> - fmt->format.field = V4L2_FIELD_NONE;
> - imx219_reset_colorspace(&fmt->format);
> -}
> + if (fse->index >= ARRAY_SIZE(supported_modes))
> + return -EINVAL;
>
> -static int __imx219_get_pad_format(struct imx219 *imx219,
> - struct v4l2_subdev_state *sd_state,
> - struct v4l2_subdev_format *fmt)
> -{
> - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
> - struct v4l2_mbus_framefmt *try_fmt =
> - v4l2_subdev_get_try_format(&imx219->sd, sd_state,
> - fmt->pad);
> - /* update the code which could change due to vflip or hflip: */
> - try_fmt->code = imx219_get_format_code(imx219, try_fmt->code);
> - fmt->format = *try_fmt;
> + fse->min_width = supported_modes[fse->index].width;
> + fse->max_width = fse->min_width;
> + fse->max_height = supported_modes[fse->index].height;
> + fse->min_height = fse->max_height;
> } else {
> - imx219_update_pad_format(imx219, imx219->mode, fmt);
> - fmt->format.code = imx219_get_format_code(imx219,
> - imx219->fmt.code);
> + if (fse->code != MEDIA_BUS_FMT_METADATA_8)
> + return -EINVAL;
> +
> + fse->min_width = IMX219_EMBEDDED_LINE_WIDTH;
> + fse->max_width = fse->min_width;
> + fse->min_height = IMX219_NUM_EMBEDDED_LINES;
> + fse->max_height = fse->min_height;
> }
>
> return 0;
> }
>
> -static int imx219_get_pad_format(struct v4l2_subdev *sd,
> - struct v4l2_subdev_state *sd_state,
> - struct v4l2_subdev_format *fmt)
> +static void imx219_update_metadata_pad_format(struct v4l2_subdev_format *fmt)
> {
> - struct imx219 *imx219 = to_imx219(sd);
> - int ret;
> -
> - mutex_lock(&imx219->mutex);
> - ret = __imx219_get_pad_format(imx219, sd_state, fmt);
> - mutex_unlock(&imx219->mutex);
> -
> - return ret;
> + fmt->format.width = IMX219_EMBEDDED_LINE_WIDTH;
> + fmt->format.height = IMX219_NUM_EMBEDDED_LINES;
> + fmt->format.code = MEDIA_BUS_FMT_METADATA_8;
> + fmt->format.field = V4L2_FIELD_NONE;
> }
>
> static int imx219_set_pad_format(struct v4l2_subdev *sd,
> @@ -966,32 +931,58 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd,
> struct v4l2_subdev_format *fmt)
> {
> struct imx219 *imx219 = to_imx219(sd);
> + struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd);
> const struct imx219_mode *mode;
> - struct v4l2_mbus_framefmt *framefmt;
> + struct v4l2_mbus_framefmt *format;
> int exposure_max, exposure_def, hblank;
> unsigned int i;
> + int ret = 0;
>
> mutex_lock(&imx219->mutex);
>
> + if (fmt->pad != 0) {
> + dev_err(&client->dev, "%s Could not get pad %d\n", __func__,
> + fmt->pad);

Drop this message.

> + ret = -EINVAL;
> + goto done;
> + }
> +
> + if (fmt->stream == 1) {
> + /* Only one embedded data mode is supported */
> + imx219_update_metadata_pad_format(fmt);

return v4l2_subdev_get_fmt(sd, sd_state, fmt);

will be simpler. You can drop the imx219_update_metadata_pad_format()
function. And move the mutex_lock() lower, after all the code that
doesn't touch fields of the imx219 structure.

> + ret = 0;

Not needed.

> + goto done;
> + }
> +
> for (i = 0; i < ARRAY_SIZE(imx219_mbus_formats); i++)
> if (imx219_mbus_formats[i] == fmt->format.code)
> break;
> if (i >= ARRAY_SIZE(imx219_mbus_formats))
> i = 0;
>
> - /* Bayer order varies with flips */
> - fmt->format.code = imx219_get_format_code(imx219, imx219_mbus_formats[i]);
> -
> mode = v4l2_find_nearest_size(supported_modes,
> ARRAY_SIZE(supported_modes),
> width, height,
> fmt->format.width, fmt->format.height);
> - imx219_update_pad_format(imx219, mode, fmt);
> - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
> - framefmt = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad);
> - *framefmt = fmt->format;
> - } else if (imx219->mode != mode ||
> - imx219->fmt.code != fmt->format.code) {
> +
> + v4l2_subdev_lock_state(sd_state);
> +
> + /* Update the stored format and return it. */
> + format = v4l2_state_get_stream_format(sd_state, fmt->pad, fmt->stream);
> +
> + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE && imx219->streaming) {
> + ret = -EBUSY;
> + goto err_state;
> + }
> +
> + /* Bayer order varies with flips */
> + format->code = imx219_get_format_code(imx219, imx219_mbus_formats[i]);
> + format->width = mode->width;
> + format->height = mode->height;
> + /* Bayer order varies with flips */
> + fmt->format = *format;
> +
> + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
> imx219->fmt = fmt->format;

The whole point of using the active state is that you don't need to
store the format in the imx219 structure anymore.

This patch does too much. I'm tempted to ask you to first switch to
usage of the active state, even before adding routing support, then add
routing support without the embedded data stream, and finally add
embedded data support.

> imx219->mode = mode;
> /* Update limits and set FPS to default */
> @@ -1018,9 +1009,12 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd,
> hblank);
> }
>
> +err_state:
> + v4l2_subdev_unlock_state(sd_state);
> +done:
> mutex_unlock(&imx219->mutex);
>
> - return 0;
> + return ret;
> }
>
> static int imx219_set_framefmt(struct imx219 *imx219)
> @@ -1322,7 +1316,7 @@ static const struct v4l2_subdev_video_ops imx219_video_ops = {
> static const struct v4l2_subdev_pad_ops imx219_pad_ops = {
> .init_cfg = imx219_init_cfg,
> .enum_mbus_code = imx219_enum_mbus_code,
> - .get_fmt = imx219_get_pad_format,
> + .get_fmt = v4l2_subdev_get_fmt,
> .set_fmt = imx219_set_pad_format,
> .get_selection = imx219_get_selection,
> .set_routing = imx219_set_routing,
> @@ -1602,10 +1596,16 @@ static int imx219_probe(struct i2c_client *client)
> goto error_handler_free;
> }
>
> + ret = v4l2_subdev_init_finalize(sd);
> + if (ret) {
> + dev_err(dev, "failed to finalize sensor init: %d\n", ret);
> + goto error_media_entity;
> + }
> +
> ret = v4l2_async_register_subdev_sensor(sd);
> if (ret < 0) {
> dev_err(dev, "failed to register sensor sub-device: %d\n", ret);
> - goto error_media_entity;
> + goto error_free_state;
> }
>
> /* Enable runtime PM and turn off the device */
> @@ -1615,6 +1615,8 @@ static int imx219_probe(struct i2c_client *client)
>
> return 0;
>
> +error_free_state:

error_subdev_cleanup:

> + v4l2_subdev_cleanup(sd);
> error_media_entity:
> media_entity_cleanup(&sd->entity);
>
> @@ -1633,6 +1635,7 @@ static int imx219_remove(struct i2c_client *client)
> struct imx219 *imx219 = to_imx219(sd);
>
> v4l2_async_unregister_subdev(sd);
> + v4l2_subdev_cleanup(sd);
> media_entity_cleanup(&sd->entity);
> imx219_free_controls(imx219);
>

--
Regards,

Laurent Pinchart

2022-02-21 09:54:29

by Stefan Wahren

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Hi Jean-Michel,

here are just some nits

Am 08.02.22 um 16:50 schrieb Jean-Michel Hautbois:
> Add driver for the Unicam camera receiver block on BCM283x processors.
> It is represented as two video device nodes: unicam-image and
> unicam-embedded which are connected to an internal subdev (named
> unicam-subdev) in order to manage streams routing.
>
> Signed-off-by: Dave Stevenson <[email protected]>
> Signed-off-by: Naushir Patuck <[email protected]>
> Signed-off-by: Jean-Michel Hautbois <[email protected]>
>
> ---
> v4:
> - Add the vendor prefox for DT name
> - Use the reg-names in DT parsing
> - Remove MAINTAINERS entry
>
> v3 main changes:
> - Change code organization
> - Remove unused variables
> - Correct the fmt_meta functions
> - Rewrite the start/stop streaming
> - You can now start the image node alone, but not the metadata one
> - The buffers are allocated per-node
> - only the required stream is started, if the route exists and is
> enabled
> - Prefix the macros with UNICAM_ to not have too generic names
> - Drop colorspace support
> -> This is causing issues in the try-fmt v4l2-compliance test
> test VIDIOC_G_FMT: OK
> fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> test VIDIOC_TRY_FMT: FAIL
> fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> test VIDIOC_S_FMT: FAIL
>
> v2: Remove the unicam_{info,debug,error} macros and use
> dev_dbg/dev_err instead.
> ---
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 2 +
> drivers/media/platform/bcm2835/Kconfig | 21 +
> drivers/media/platform/bcm2835/Makefile | 3 +
> .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> 6 files changed, 2850 insertions(+)
> create mode 100644 drivers/media/platform/bcm2835/Kconfig
> create mode 100644 drivers/media/platform/bcm2835/Makefile
> create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c
>
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 9fbdba0fd1e7..033b0358fbb8 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -170,6 +170,7 @@ source "drivers/media/platform/am437x/Kconfig"
> source "drivers/media/platform/xilinx/Kconfig"
> source "drivers/media/platform/rcar-vin/Kconfig"
> source "drivers/media/platform/atmel/Kconfig"
> +source "drivers/media/platform/bcm2835/Kconfig"
> source "drivers/media/platform/sunxi/Kconfig"
>
> config VIDEO_TI_CAL
> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> index 28eb4aadbf45..18894fc586aa 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -86,6 +86,8 @@ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom/camss/
>
> obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
>
> +obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835/
> +
> obj-y += sunxi/
>
> obj-$(CONFIG_VIDEO_MESON_GE2D) += meson/ge2d/
> diff --git a/drivers/media/platform/bcm2835/Kconfig b/drivers/media/platform/bcm2835/Kconfig
> new file mode 100644
> index 000000000000..1691541da905
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/Kconfig
> @@ -0,0 +1,21 @@
> +# Broadcom VideoCore4 V4L2 camera support
> +
> +config VIDEO_BCM2835_UNICAM
> + tristate "Broadcom BCM283x/BCM271x Unicam video capture driver"
> + depends on ARCH_BCM2835 || COMPILE_TEST
> + depends on VIDEO_V4L2
> + select MEDIA_CONTROLLER
> + select VIDEO_V4L2_SUBDEV_API
> + select V4L2_FWNODE
> + select VIDEOBUF2_DMA_CONTIG
> + help
> + Say Y here to enable support for the BCM283x/BCM271x CSI-2 receiver.
> + This is a V4L2 driver that controls the CSI-2 receiver directly,
> + independently from the VC4 firmware.
> + This driver is mutually exclusive with the use of bcm2835-camera. The
> + firmware will disable all access to the peripheral from within the
> + firmware if it finds a DT node using it, and bcm2835-camera will
> + therefore fail to probe.
> +
> + To compile this driver as a module, choose M here. The module will be
> + called bcm2835-unicam.
> diff --git a/drivers/media/platform/bcm2835/Makefile b/drivers/media/platform/bcm2835/Makefile
> new file mode 100644
> index 000000000000..a98aba03598a
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/Makefile
> @@ -0,0 +1,3 @@
> +# Makefile for BCM2835 Unicam driver
> +
> +obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835-unicam.o
> diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> new file mode 100644
> index 000000000000..b8d297076a02
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> @@ -0,0 +1,253 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +
> +/*
> + * Copyright (C) 2017-2020 Raspberry Pi Trading.
> + * Dave Stevenson <[email protected]>
> + */
> +
> +#ifndef VC4_REGS_UNICAM_H
> +#define VC4_REGS_UNICAM_H
> +
> +/*
> + * The following values are taken from files found within the code drop
> + * made by Broadcom for the BCM21553 Graphics Driver, predominantly in
> + * brcm_usrlib/dag/vmcsx/vcinclude/hardware_vc4.h.
> + * They have been modified to be only the register offset.
> + */
> +#define UNICAM_CTRL 0x000
> +#define UNICAM_STA 0x004
> +#define UNICAM_ANA 0x008
> +#define UNICAM_PRI 0x00c
> +#define UNICAM_CLK 0x010
> +#define UNICAM_CLT 0x014
> +#define UNICAM_DAT0 0x018
> +#define UNICAM_DAT1 0x01c
> +#define UNICAM_DAT2 0x020
> +#define UNICAM_DAT3 0x024
> +#define UNICAM_DLT 0x028
> +#define UNICAM_CMP0 0x02c
> +#define UNICAM_CMP1 0x030
> +#define UNICAM_CAP0 0x034
> +#define UNICAM_CAP1 0x038
> +#define UNICAM_ICTL 0x100
> +#define UNICAM_ISTA 0x104
> +#define UNICAM_IDI0 0x108
> +#define UNICAM_IPIPE 0x10c
> +#define UNICAM_IBSA0 0x110
> +#define UNICAM_IBEA0 0x114
> +#define UNICAM_IBLS 0x118
> +#define UNICAM_IBWP 0x11c
> +#define UNICAM_IHWIN 0x120
> +#define UNICAM_IHSTA 0x124
> +#define UNICAM_IVWIN 0x128
> +#define UNICAM_IVSTA 0x12c
> +#define UNICAM_ICC 0x130
> +#define UNICAM_ICS 0x134
> +#define UNICAM_IDC 0x138
> +#define UNICAM_IDPO 0x13c
> +#define UNICAM_IDCA 0x140
> +#define UNICAM_IDCD 0x144
> +#define UNICAM_IDS 0x148
> +#define UNICAM_DCS 0x200
> +#define UNICAM_DBSA0 0x204
> +#define UNICAM_DBEA0 0x208
> +#define UNICAM_DBWP 0x20c
> +#define UNICAM_DBCTL 0x300
> +#define UNICAM_IBSA1 0x304
> +#define UNICAM_IBEA1 0x308
> +#define UNICAM_IDI1 0x30c
> +#define UNICAM_DBSA1 0x310
> +#define UNICAM_DBEA1 0x314
> +#define UNICAM_MISC 0x400
> +
> +/*
> + * The following bitmasks are from the kernel released by Broadcom
> + * for Android - https://android.googlesource.com/kernel/bcm/
> + * The Rhea, Hawaii, and Java chips all contain the same VideoCore4
> + * Unicam block as BCM2835, as defined in eg
> + * arch/arm/mach-rhea/include/mach/rdb_A0/brcm_rdb_cam.h and similar.
> + * Values reworked to use the kernel BIT and GENMASK macros.
> + *
> + * Some of the bit mnenomics have been amended to match the datasheet.
> + */
> +/* UNICAM_CTRL Register */
> +#define UNICAM_CPE BIT(0)
> +#define UNICAM_MEM BIT(1)
> +#define UNICAM_CPR BIT(2)
> +#define UNICAM_CPM_MASK GENMASK(3, 3)
> +#define UNICAM_CPM_CSI2 0
> +#define UNICAM_CPM_CCP2 1
> +#define UNICAM_SOE BIT(4)
> +#define UNICAM_DCM_MASK GENMASK(5, 5)
> +#define UNICAM_DCM_STROBE 0
> +#define UNICAM_DCM_DATA 1
> +#define UNICAM_SLS BIT(6)
> +#define UNICAM_PFT_MASK GENMASK(11, 8)
> +#define UNICAM_OET_MASK GENMASK(20, 12)
> +
> +/* UNICAM_STA Register */
> +#define UNICAM_SYN BIT(0)
> +#define UNICAM_CS BIT(1)
> +#define UNICAM_SBE BIT(2)
> +#define UNICAM_PBE BIT(3)
> +#define UNICAM_HOE BIT(4)
> +#define UNICAM_PLE BIT(5)
> +#define UNICAM_SSC BIT(6)
> +#define UNICAM_CRCE BIT(7)
> +#define UNICAM_OES BIT(8)
> +#define UNICAM_IFO BIT(9)
> +#define UNICAM_OFO BIT(10)
> +#define UNICAM_BFO BIT(11)
> +#define UNICAM_DL BIT(12)
> +#define UNICAM_PS BIT(13)
> +#define UNICAM_IS BIT(14)
> +#define UNICAM_PI0 BIT(15)
> +#define UNICAM_PI1 BIT(16)
> +#define UNICAM_FSI_S BIT(17)
> +#define UNICAM_FEI_S BIT(18)
> +#define UNICAM_LCI_S BIT(19)
> +#define UNICAM_BUF0_RDY BIT(20)
> +#define UNICAM_BUF0_NO BIT(21)
> +#define UNICAM_BUF1_RDY BIT(22)
> +#define UNICAM_BUF1_NO BIT(23)
> +#define UNICAM_DI BIT(24)
> +
> +#define UNICAM_STA_MASK_ALL \
> + (UNICAM_DL | \
> + UNICAM_SBE | \
> + UNICAM_PBE | \
> + UNICAM_HOE | \
> + UNICAM_PLE | \
> + UNICAM_SSC | \
> + UNICAM_CRCE | \
> + UNICAM_IFO | \
> + UNICAM_OFO | \
> + UNICAM_PS | \
> + UNICAM_PI0 | \
> + UNICAM_PI1)
> +
> +/* UNICAM_ANA Register */
> +#define UNICAM_APD BIT(0)
> +#define UNICAM_BPD BIT(1)
> +#define UNICAM_AR BIT(2)
> +#define UNICAM_DDL BIT(3)
> +#define UNICAM_CTATADJ_MASK GENMASK(7, 4)
> +#define UNICAM_PTATADJ_MASK GENMASK(11, 8)
> +
> +/* UNICAM_PRI Register */
> +#define UNICAM_PE BIT(0)
> +#define UNICAM_PT_MASK GENMASK(2, 1)
> +#define UNICAM_NP_MASK GENMASK(7, 4)
> +#define UNICAM_PP_MASK GENMASK(11, 8)
> +#define UNICAM_BS_MASK GENMASK(15, 12)
> +#define UNICAM_BL_MASK GENMASK(17, 16)
> +
> +/* UNICAM_CLK Register */
> +#define UNICAM_CLE BIT(0)
> +#define UNICAM_CLPD BIT(1)
> +#define UNICAM_CLLPE BIT(2)
> +#define UNICAM_CLHSE BIT(3)
> +#define UNICAM_CLTRE BIT(4)
> +#define UNICAM_CLAC_MASK GENMASK(8, 5)
> +#define UNICAM_CLSTE BIT(29)
> +
> +/* UNICAM_CLT Register */
> +#define UNICAM_CLT1_MASK GENMASK(7, 0)
> +#define UNICAM_CLT2_MASK GENMASK(15, 8)
> +
> +/* UNICAM_DATn Registers */
> +#define UNICAM_DLE BIT(0)
> +#define UNICAM_DLPD BIT(1)
> +#define UNICAM_DLLPE BIT(2)
> +#define UNICAM_DLHSE BIT(3)
> +#define UNICAM_DLTRE BIT(4)
> +#define UNICAM_DLSM BIT(5)
> +#define UNICAM_DLFO BIT(28)
> +#define UNICAM_DLSTE BIT(29)
> +
> +#define UNICAM_DAT_MASK_ALL (UNICAM_DLSTE | UNICAM_DLFO)
> +
> +/* UNICAM_DLT Register */
> +#define UNICAM_DLT1_MASK GENMASK(7, 0)
> +#define UNICAM_DLT2_MASK GENMASK(15, 8)
> +#define UNICAM_DLT3_MASK GENMASK(23, 16)
> +
> +/* UNICAM_ICTL Register */
> +#define UNICAM_FSIE BIT(0)
> +#define UNICAM_FEIE BIT(1)
> +#define UNICAM_IBOB BIT(2)
> +#define UNICAM_FCM BIT(3)
> +#define UNICAM_TFC BIT(4)
> +#define UNICAM_LIP_MASK GENMASK(6, 5)
> +#define UNICAM_LCIE_MASK GENMASK(28, 16)
> +
> +/* UNICAM_IDI0/1 Register */
> +#define UNICAM_ID0_MASK GENMASK(7, 0)
> +#define UNICAM_ID1_MASK GENMASK(15, 8)
> +#define UNICAM_ID2_MASK GENMASK(23, 16)
> +#define UNICAM_ID3_MASK GENMASK(31, 24)
> +
> +/* UNICAM_ISTA Register */
> +#define UNICAM_FSI BIT(0)
> +#define UNICAM_FEI BIT(1)
> +#define UNICAM_LCI BIT(2)
> +
> +#define UNICAM_ISTA_MASK_ALL (UNICAM_FSI | UNICAM_FEI | UNICAM_LCI)
> +
> +/* UNICAM_IPIPE Register */
> +#define UNICAM_PUM_MASK GENMASK(2, 0)
> +/* Unpacking modes */
> +#define UNICAM_PUM_NONE 0
> +#define UNICAM_PUM_UNPACK6 1
> +#define UNICAM_PUM_UNPACK7 2
> +#define UNICAM_PUM_UNPACK8 3
> +#define UNICAM_PUM_UNPACK10 4
> +#define UNICAM_PUM_UNPACK12 5
> +#define UNICAM_PUM_UNPACK14 6
> +#define UNICAM_PUM_UNPACK16 7
> +#define UNICAM_DDM_MASK GENMASK(6, 3)
> +#define UNICAM_PPM_MASK GENMASK(9, 7)
> +/* Packing modes */
> +#define UNICAM_PPM_NONE 0
> +#define UNICAM_PPM_PACK8 1
> +#define UNICAM_PPM_PACK10 2
> +#define UNICAM_PPM_PACK12 3
> +#define UNICAM_PPM_PACK14 4
> +#define UNICAM_PPM_PACK16 5
> +#define UNICAM_DEM_MASK GENMASK(11, 10)
> +#define UNICAM_DEBL_MASK GENMASK(14, 12)
> +#define UNICAM_ICM_MASK GENMASK(16, 15)
> +#define UNICAM_IDM_MASK GENMASK(17, 17)
> +
> +/* UNICAM_ICC Register */
> +#define UNICAM_ICFL_MASK GENMASK(4, 0)
> +#define UNICAM_ICFH_MASK GENMASK(9, 5)
> +#define UNICAM_ICST_MASK GENMASK(12, 10)
> +#define UNICAM_ICLT_MASK GENMASK(15, 13)
> +#define UNICAM_ICLL_MASK GENMASK(31, 16)
> +
> +/* UNICAM_DCS Register */
> +#define UNICAM_DIE BIT(0)
> +#define UNICAM_DIM BIT(1)
> +#define UNICAM_DBOB BIT(3)
> +#define UNICAM_FDE BIT(4)
> +#define UNICAM_LDP BIT(5)
> +#define UNICAM_EDL_MASK GENMASK(15, 8)
> +
> +/* UNICAM_DBCTL Register */
> +#define UNICAM_DBEN BIT(0)
> +#define UNICAM_BUF0_IE BIT(1)
> +#define UNICAM_BUF1_IE BIT(2)
> +
> +/* UNICAM_CMP[0,1] register */
> +#define UNICAM_PCE BIT(31)
> +#define UNICAM_GI BIT(9)
> +#define UNICAM_CPH BIT(8)
> +#define UNICAM_PCVC_MASK GENMASK(7, 6)
> +#define UNICAM_PCDT_MASK GENMASK(5, 0)
> +
> +/* UNICAM_MISC register */
> +#define UNICAM_FL0 BIT(6)
> +#define UNICAM_FL1 BIT(9)
> +
> +#endif
> diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam.c b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> new file mode 100644
> index 000000000000..470e691637c7
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> @@ -0,0 +1,2570 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * BCM283x / BCM271x Unicam Capture Driver
> + *
> + * Copyright (C) 2017-2020 - Raspberry Pi (Trading) Ltd.
> + *
> + * Dave Stevenson <[email protected]>
> + *
> + * Based on TI am437x driver by
> + * Benoit Parrot <[email protected]>
> + * Lad, Prabhakar <[email protected]>
> + *
> + * and TI CAL camera interface driver by
> + * Benoit Parrot <[email protected]>
> + *
> + *
> + * There are two camera drivers in the kernel for BCM283x - this one
> + * and bcm2835-camera (currently in staging).
> + *
> + * This driver directly controls the Unicam peripheral - there is no
> + * involvement with the VideoCore firmware. Unicam receives CSI-2 or
> + * CCP2 data and writes it into SDRAM.
> + * The only potential processing options are to repack Bayer data into an
> + * alternate format, and applying windowing.
> + * The repacking does not shift the data, so can repack V4L2_PIX_FMT_Sxxxx10P
> + * to V4L2_PIX_FMT_Sxxxx10, or V4L2_PIX_FMT_Sxxxx12P to V4L2_PIX_FMT_Sxxxx12,
> + * but not generically up to V4L2_PIX_FMT_Sxxxx16. The driver will add both
> + * formats where the relevant formats are defined, and will automatically
> + * configure the repacking as required.
> + * Support for windowing may be added later.
> + *
> + * It should be possible to connect this driver to any sensor with a
> + * suitable output interface and V4L2 subdevice driver.
> + *
> + * bcm2835-camera uses the VideoCore firmware to control the sensor,
> + * Unicam, ISP, and all tuner control loops. Fully processed frames are
> + * delivered to the driver by the firmware. It only has sensor drivers
> + * for Omnivision OV5647, and Sony IMX219 sensors.
> + *
> + * The two drivers are mutually exclusive for the same Unicam instance.
> + * The VideoCore firmware checks the device tree configuration during boot.
> + * If it finds device tree nodes called csi0 or csi1 it will block the
> + * firmware from accessing the peripheral, and bcm2835-camera will
> + * not be able to stream data.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/of_graph.h>
> +#include <linux/pinctrl/consumer.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +#include <linux/videodev2.h>
> +
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-dv-timings.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "bcm2835-unicam-regs.h"
> +
> +#define UNICAM_MODULE_NAME "unicam"
> +
...
> +
> +static int unicam_sd_enable_streams(struct v4l2_subdev *sd, u32 pad,
> + u64 streams_mask)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_subdev_state *state;
> + u32 other_pad, other_stream;
> + int ret;
> +
> + if (WARN_ON(streams_mask != 1))
> + return -EINVAL;
Can we print some error message additionally to the stacktrace?
> +
> + state = v4l2_subdev_lock_active_state(sd);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
> + &other_pad, &other_stream);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + if (ret)
> + return ret;
> +
> + unicam->sequence = 0;
> +
> + dev_dbg(unicam->dev, "Running with %u data lanes\n",
> + unicam->active_data_lanes);
> +
> + ret = clk_set_min_rate(unicam->vpu_clock, UNICAM_MIN_VPU_CLOCK_RATE);
> + if (ret) {
> + dev_err(unicam->dev, "failed to set up VPU clock\n");
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(unicam->vpu_clock);
> + if (ret) {
> + dev_err(unicam->dev, "Failed to enable VPU clock: %d\n", ret);
> + goto err_vpu_clock;
> + }
> +
> + ret = clk_set_rate(unicam->clock, 100 * 1000 * 1000);
> + if (ret) {
> + dev_err(unicam->dev, "failed to set up CSI clock\n");
> + goto err_vpu_prepare;
> + }
> +
> + ret = clk_prepare_enable(unicam->clock);
> + if (ret) {
> + dev_err(unicam->dev, "Failed to enable CSI clock: %d\n", ret);
> + goto err_vpu_prepare;
> + }
> +
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, enable_streams,
> + unicam->sensor.pad->index, BIT(other_stream));
> +
> + if (ret && ret == -ENOIOCTLCMD)
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, s_stream, 1);
> +
> + if (ret) {
> + dev_err(unicam->dev, "stream on failed in subdev\n");
> + goto err_enable_stream;
> + }
> +
> + unicam->subdev.streaming = true;
> +
> + return 0;
> +
> +err_enable_stream:
> + clk_disable_unprepare(unicam->clock);
> +err_vpu_prepare:
> + clk_disable_unprepare(unicam->vpu_clock);
> +err_vpu_clock:
> + if (clk_set_min_rate(unicam->vpu_clock, 0))
> + dev_err(unicam->dev, "failed to reset the VPU clock\n");
> + return ret;
> +}
> +
> +static int unicam_sd_disable_streams(struct v4l2_subdev *sd, u32 pad,
> + u64 streams_mask)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_subdev_state *state;
> + u32 other_pad, other_stream;
> + int ret;
> +
> + if (WARN_ON(streams_mask != 1))
> + return -EINVAL;
Can we print some error message additionally to the stacktrace?
> +
> + state = v4l2_subdev_lock_active_state(sd);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
> + &other_pad, &other_stream);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + if (ret) {
> + dev_err(unicam->dev, "disable streams failed: %d\n", ret);
> + return ret;
> + }
> +
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, disable_streams,
> + unicam->sensor.pad->index, BIT(other_stream));
> +
> + if (ret && ret == -ENOIOCTLCMD) {
> + /* The sensor does not support disable streams. */
> + if (unicam->node[UNICAM_IMAGE_NODE].streaming &&
> + other_pad == unicam->node[UNICAM_METADATA_NODE].pad.index) {
> + /* We won't stop the sensor yet. */
> + unicam->subdev.streaming = false;
> + return 0;
> + }
> + /* There is no more the metadata node, we can stop. */
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, s_stream, 0);
> + }
> +
> + clk_disable_unprepare(unicam->clock);
> + if (clk_set_min_rate(unicam->vpu_clock, 0))
> + dev_err(unicam->dev, "failed to reset the VPU clock\n");
> + clk_disable_unprepare(unicam->vpu_clock);
> +
> + unicam->subdev.streaming = false;
> +
> + return 0;
> +}
> +
> +static int unicam_subdev_set_pad_format(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_format *format)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_mbus_framefmt *sink_format, *source_format;
> + const struct unicam_fmt *fmtinfo;
> + int ret = 0;
> +
> + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && unicam->subdev.streaming)
> + return -EBUSY;
> +
> + /* No transcoding, source and sink formats must match. */
> + if (unicam_sd_pad_is_source(format->pad))
> + return v4l2_subdev_get_fmt(sd, state, format);
> +
> + fmtinfo = unicam_find_format_by_code(format->format.code, format->pad);
> + if (!fmtinfo)
> + fmtinfo = &unicam_image_formats[0];
> +
> + if (format->pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + format->format.width = clamp_t(unsigned int,
> + format->format.width,
> + UNICAM_MIN_WIDTH,
> + UNICAM_MAX_WIDTH);
> + format->format.height = clamp_t(unsigned int,
> + format->format.height,
> + UNICAM_MIN_HEIGHT,
> + UNICAM_MAX_HEIGHT);
> + }
> + format->format.field = V4L2_FIELD_NONE;
> +
> + v4l2_subdev_lock_state(state);
> +
> + sink_format = v4l2_state_get_stream_format(state, format->pad,
> + format->stream);
> + source_format = v4l2_subdev_state_get_opposite_stream_format(state,
> + format->pad,
> + format->stream);
> + if (!sink_format || !source_format) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + *sink_format = format->format;
> + *source_format = format->format;
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_subdev_enum_frame_size(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_frame_size_enum *fse)
> +{
> + const struct unicam_fmt *fmtinfo;
> + u32 pad, stream;
> + int ret = -EINVAL;
> +
> + if (fse->index > 0)
> + return ret;
> +
> + v4l2_subdev_lock_state(state);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, fse->pad,
> + fse->stream, &pad,
> + &stream);
> + if (ret)
> + goto out;
> +
> + if (unicam_sd_pad_is_source(fse->pad)) {
> + /* No transcoding, source and sink formats must match. */
> + struct v4l2_mbus_framefmt *fmt;
> +
> + fmt = v4l2_state_get_stream_format(state, pad, stream);
> + if (!fmt)
> + goto out;
> +
> + if (fse->code != fmt->code)
> + goto out;
> +
> + fse->min_width = fmt->width;
> + fse->max_width = fmt->width;
> + fse->min_height = fmt->height;
> + fse->max_height = fmt->height;
> + } else {
> + fmtinfo = unicam_find_format_by_code(fse->code, pad);
> + if (!fmtinfo)
> + goto out;
> +
> + fse->min_width = UNICAM_MIN_WIDTH;
> + fse->max_width = UNICAM_MAX_WIDTH;
> + fse->min_height = UNICAM_MIN_HEIGHT;
> + fse->max_height = UNICAM_MAX_HEIGHT;
> + }
> +
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static const struct v4l2_subdev_video_ops unicam_subdev_video_ops = {
> + .enable_streams = unicam_sd_enable_streams,
> + .disable_streams = unicam_sd_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_pad_ops unicam_subdev_pad_ops = {
> + .init_cfg = unicam_subdev_init_cfg,
> + .enum_mbus_code = unicam_subdev_enum_mbus_code,
> + .get_fmt = v4l2_subdev_get_fmt,
> + .set_fmt = unicam_subdev_set_pad_format,
> + .set_routing = unicam_subdev_set_routing,
> + .enum_frame_size = unicam_subdev_enum_frame_size,
> +};
> +
> +static const struct v4l2_subdev_ops unicam_subdev_ops = {
> + .video = &unicam_subdev_video_ops,
> + .pad = &unicam_subdev_pad_ops,
> +};
> +
> +static const struct media_entity_operations unicam_subdev_media_ops = {
> + .link_validate = v4l2_subdev_link_validate,
> + .has_route = v4l2_subdev_has_route,
> +};
> +
> +static int unicam_init_and_register_subdev(struct unicam_device *unicam)
> +{
> + struct v4l2_subdev *sd = &unicam->subdev.sd;
> + int ret;
> +
> + v4l2_subdev_init(sd, &unicam_subdev_ops);
> + v4l2_set_subdevdata(sd, unicam);
> + unicam->subdev.sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> + unicam->subdev.sd.entity.ops = &unicam_subdev_media_ops;
> + unicam->subdev.sd.dev = unicam->dev;
> + unicam->subdev.sd.owner = THIS_MODULE;
> + unicam->subdev.sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_MULTIPLEXED;
> + snprintf(unicam->subdev.sd.name, sizeof(unicam->subdev.sd.name), "unicam-subdev");
> +
> + unicam->subdev.pads[UNICAM_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +
> + unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_IMAGE].flags = MEDIA_PAD_FL_SOURCE;
> + unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_METADATA].flags = MEDIA_PAD_FL_SOURCE;
> +
> + ret = media_entity_pads_init(&sd->entity,
> + ARRAY_SIZE(unicam->subdev.pads), unicam->subdev.pads);
> + if (ret) {
> + dev_err(unicam->dev, "Could not init media entity");
> + return ret;
> + }
> +
> + ret = v4l2_subdev_init_finalize(sd);
> + if (ret) {
> + dev_err(unicam->dev, "Could not finalize init for subdev");
> + return ret;
> + }
> +
> + return v4l2_device_register_subdev(&unicam->v4l2_dev, sd);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Videobuf2 Queue Operations
> + */
> +
> +static int unicam_queue_setup(struct vb2_queue *vq,
> + unsigned int *nbuffers,
> + unsigned int *nplanes,
> + unsigned int sizes[],
> + struct device *alloc_devs[])
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vq);
> + struct unicam_device *unicam = node->dev;
> + unsigned int size = is_image_node(node) ?
> + node->v_fmt.fmt.pix.sizeimage :
> + node->v_fmt.fmt.meta.buffersize;
> +
> + if (vq->num_buffers + *nbuffers < 3)
> + *nbuffers = 3 - vq->num_buffers;
> +
> + if (*nplanes) {
> + if (sizes[0] < size) {
> + dev_dbg(unicam->dev, "sizes[0] %i < size %u\n", sizes[0],
> + size);
> + return -EINVAL;
> + }
> + size = sizes[0];
> + }
> +
> + *nplanes = 1;
> + sizes[0] = size;
> +
> + return 0;
> +}
> +
> +static int unicam_buffer_prepare(struct vb2_buffer *vb)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
> + struct unicam_device *unicam = node->dev;
> + struct unicam_buffer *buf = to_unicam_buffer(vb);
> + unsigned long size;
> +
> + if (WARN_ON(!node->fmt))
> + return -EINVAL;
Can we print some error message additionally to the stacktrace?
> +
> + size = is_image_node(node) ? node->v_fmt.fmt.pix.sizeimage :
> + node->v_fmt.fmt.meta.buffersize;
> + if (vb2_plane_size(vb, 0) < size) {
> + dev_dbg(unicam->dev, "data will not fit into plane (%lu < %lu)\n",
> + vb2_plane_size(vb, 0), size);
> + return -EINVAL;
> + }
> +
> + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
> + return 0;
> +}
> +
...
> +
> +static int unicam_connect_of_subdevs(struct unicam_device *unicam)
> +{
> + struct v4l2_fwnode_endpoint ep = { };
> + struct fwnode_handle *ep_handle;
> + struct v4l2_async_subdev *asd;
> + unsigned int lane;
> + int ret = -EINVAL;
> +
> + if (of_property_read_u32(unicam->dev->of_node, "brcm,num-data-lanes",
> + &unicam->max_data_lanes) < 0) {
> + dev_err(unicam->dev, "DT property %s not set\n",
> + "brcm,num-data-lanes");
> + return -EINVAL;
> + }
> +
> + /* Get the local endpoint and remote device. */
> + ep_handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(unicam->dev),
> + 0, 0,
> + FWNODE_GRAPH_ENDPOINT_NEXT);
> + if (!ep_handle) {
> + dev_err(unicam->dev, "No endpoint\n");
> + return -ENODEV;
> + }
> +
> + /* Parse the local endpoint and validate its configuration. */
> + if (v4l2_fwnode_endpoint_alloc_parse(ep_handle, &ep)) {
> + dev_err(unicam->dev, "could not parse endpoint\n");
> + goto cleanup_exit;
> + }
> +
> + dev_dbg(unicam->dev, "parsed local endpoint, bus_type %u\n",
> + ep.bus_type);
> +
> + unicam->bus_type = ep.bus_type;
> +
> + switch (ep.bus_type) {
> + case V4L2_MBUS_CSI2_DPHY:
> + switch (ep.bus.mipi_csi2.num_data_lanes) {
> + case 1:
> + case 2:
> + case 4:
> + break;
> +
> + default:
> + dev_err(unicam->dev, "%u data lanes not supported\n",
> + ep.bus.mipi_csi2.num_data_lanes);
> + goto cleanup_exit;
> + }
> +
> + for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
> + if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
> + dev_err(unicam->dev, "data lanes reordering not supported\n");
> + goto cleanup_exit;
> + }
> + }
> +
> + if (ep.bus.mipi_csi2.num_data_lanes > unicam->max_data_lanes) {
> + dev_err(unicam->dev, "endpoint requires %u data lanes when %u are supported\n",
> + ep.bus.mipi_csi2.num_data_lanes,
> + unicam->max_data_lanes);
> + }
> +
> + unicam->active_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
> + unicam->bus_flags = ep.bus.mipi_csi2.flags;
> +
> + break;
> +
> + case V4L2_MBUS_CCP2:
> + if (ep.bus.mipi_csi1.clock_lane != 0 ||
> + ep.bus.mipi_csi1.data_lane != 1) {
> + dev_err(unicam->dev, "unsupported lanes configuration\n");
> + goto cleanup_exit;
> + }
> +
> + unicam->max_data_lanes = 1;
> + unicam->active_data_lanes = 1;
> + unicam->bus_flags = ep.bus.mipi_csi1.strobe;
> + break;
> +
> + default:
> + /* Unsupported bus type */
> + dev_err(unicam->dev, "unsupported bus type %u\n",
> + ep.bus_type);
> + goto cleanup_exit;
> + }
> +
> + dev_dbg(unicam->dev, "%s bus, %u data lanes, flags=0x%08x\n",
> + unicam->bus_type == V4L2_MBUS_CSI2_DPHY ? "CSI-2" : "CCP2",
> + unicam->active_data_lanes, unicam->bus_flags);
> +
> + /* Initialize and register the async notifier. */
> + v4l2_async_nf_init(&unicam->notifier);
> +
> + asd = v4l2_async_nf_add_fwnode_remote(&unicam->notifier, ep_handle,
> + struct v4l2_async_subdev);
> +
> + fwnode_handle_put(ep_handle);
> + ep_handle = NULL;
> +
> + if (IS_ERR(asd)) {
please provide a log entry here
> + ret = PTR_ERR(asd);
> + goto cleanup_exit;
> + }
> +
> + unicam->notifier.ops = &unicam_async_ops;
> +
> + ret = v4l2_async_nf_register(&unicam->v4l2_dev, &unicam->notifier);
> + if (ret) {
> + dev_err(unicam->dev, "Error registering device notifier: %d\n", ret);
> + goto cleanup_exit;
> + }
> +
> + return 0;
> +
> +cleanup_exit:
> + v4l2_fwnode_endpoint_free(&ep);
> + fwnode_handle_put(ep_handle);
> +
> + return ret;
> +}
> +
> +static int unicam_media_dev_init(struct unicam_device *unicam)
> +{
> + int ret = 0;
no need to init ret here
> +
> + unicam->mdev.dev = unicam->dev;
> + strscpy(unicam->mdev.model, UNICAM_MODULE_NAME,
> + sizeof(unicam->mdev.model));
> + strscpy(unicam->mdev.serial, "", sizeof(unicam->mdev.serial));
> + snprintf(unicam->mdev.bus_info, sizeof(unicam->mdev.bus_info),
> + "platform:%s", dev_name(unicam->dev));
> + unicam->mdev.hw_revision = 0;
> +
> + media_device_init(&unicam->mdev);
> +
> + unicam->v4l2_dev.mdev = &unicam->mdev;
> +
> + ret = v4l2_device_register(unicam->dev, &unicam->v4l2_dev);
> + if (ret < 0) {
> + dev_err(unicam->dev,
> + "Unable to register v4l2 device\n");
> + return ret;
> + }
> +
> + ret = media_device_register(&unicam->mdev);
> + if (ret < 0) {
> + dev_err(unicam->dev,
> + "Unable to register media-controller device\n");
> + goto err_v4l2_unregister;
> + }
> +
> + return 0;
> +
> +err_v4l2_unregister:
> + v4l2_device_unregister(&unicam->v4l2_dev);
> + return ret;
> +}
> +
> +static int unicam_probe(struct platform_device *pdev)
> +{
> + struct unicam_device *unicam;
> + int ret = 0;
> +
> + unicam = kzalloc(sizeof(*unicam), GFP_KERNEL);
> + if (!unicam)
> + return -ENOMEM;
> +
> + kref_init(&unicam->kref);
> + unicam->dev = &pdev->dev;
> + /* set the driver data in platform device */
> + platform_set_drvdata(pdev, unicam);
> +
> + unicam->base = devm_platform_ioremap_resource_byname(pdev, "unicam");
> + if (IS_ERR(unicam->base)) {
> + ret = PTR_ERR(unicam->base);
> + goto err_unicam_put;
> + }
> +
> + unicam->clk_gate_base = devm_platform_ioremap_resource_byname(pdev, "cmi");
> + if (IS_ERR(unicam->clk_gate_base)) {
> + ret = PTR_ERR(unicam->clk_gate_base);
> + goto err_unicam_put;
> + }
> +
> + unicam->clock = devm_clk_get(&pdev->dev, "lp");
> + if (IS_ERR(unicam->clock)) {
> + dev_err(unicam->dev, "Failed to get lp clock\n");
> + ret = PTR_ERR(unicam->clock);
> + goto err_unicam_put;
> + }
> +
> + unicam->vpu_clock = devm_clk_get(&pdev->dev, "vpu");
> + if (IS_ERR(unicam->vpu_clock)) {
> + dev_err(unicam->dev, "Failed to get vpu clock\n");
> + ret = PTR_ERR(unicam->vpu_clock);
> + goto err_unicam_put;
> + }
> +
> + ret = platform_get_irq(pdev, 0);
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "No IRQ resource\n");
> + ret = -EINVAL;
> + goto err_unicam_put;
> + }
> +
> + ret = devm_request_irq(&pdev->dev, ret, unicam_isr, 0,
> + "unicam_capture0", unicam);
> + if (ret) {
> + dev_err(&pdev->dev, "Unable to request interrupt\n");
> + ret = -EINVAL;
> + goto err_unicam_put;
> + }
> +
> + ret = unicam_media_dev_init(unicam);
> + if (ret) {
> + dev_err(unicam->dev,
> + "Unable to initialize media device\n");
> + goto err_unicam_put;
> + }
> +
> + ret = unicam_init_and_register_subdev(unicam);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to register internal subdev\n");

please drop this unnecessary log entry, because the called function
already provide more helpful log entries.

Best regards

> + goto err_media_unregister;
> + }
> +
> + ret = unicam_connect_of_subdevs(unicam);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to connect subdevs\n");
> + goto err_subdev_unregister;
> + }
> +
> + /* Enable the block power domain */
> + pm_runtime_enable(&pdev->dev);
> +
> + return 0;
> +
> +err_subdev_unregister:
> + v4l2_subdev_cleanup(&unicam->subdev.sd);
> +err_media_unregister:
> + media_device_unregister(&unicam->mdev);
> +err_unicam_put:
> + unicam_put(unicam);
> +
> + return ret;
> +}
> +
> +static int unicam_remove(struct platform_device *pdev)
> +{
> + struct unicam_device *unicam = platform_get_drvdata(pdev);
> +
> + unicam_unregister_nodes(unicam);
> + v4l2_device_unregister(&unicam->v4l2_dev);
> + media_device_unregister(&unicam->mdev);
> + v4l2_async_nf_unregister(&unicam->notifier);
> + unicam_put(unicam);
> +
> + pm_runtime_disable(&pdev->dev);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id unicam_of_match[] = {
> + { .compatible = "brcm,bcm2835-unicam", },
> + { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, unicam_of_match);
> +
> +static struct platform_driver unicam_driver = {
> + .probe = unicam_probe,
> + .remove = unicam_remove,
> + .driver = {
> + .name = UNICAM_MODULE_NAME,
> + .of_match_table = of_match_ptr(unicam_of_match),
> + },
> +};
> +
> +module_platform_driver(unicam_driver);
> +
> +MODULE_AUTHOR("Dave Stevenson <[email protected]>");
> +MODULE_DESCRIPTION("BCM2835 Unicam driver");
> +MODULE_LICENSE("GPL");

2022-02-21 17:16:02

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Hi Jean-Michel,

Thank you for the patch.

On Tue, Feb 08, 2022 at 04:50:20PM +0100, Jean-Michel Hautbois wrote:
> Add driver for the Unicam camera receiver block on BCM283x processors.
> It is represented as two video device nodes: unicam-image and
> unicam-embedded which are connected to an internal subdev (named
> unicam-subdev) in order to manage streams routing.
>
> Signed-off-by: Dave Stevenson <[email protected]>
> Signed-off-by: Naushir Patuck <[email protected]>
> Signed-off-by: Jean-Michel Hautbois <[email protected]>
>
> ---
> v4:
> - Add the vendor prefox for DT name
> - Use the reg-names in DT parsing
> - Remove MAINTAINERS entry
>
> v3 main changes:
> - Change code organization
> - Remove unused variables
> - Correct the fmt_meta functions
> - Rewrite the start/stop streaming
> - You can now start the image node alone, but not the metadata one
> - The buffers are allocated per-node
> - only the required stream is started, if the route exists and is
> enabled
> - Prefix the macros with UNICAM_ to not have too generic names
> - Drop colorspace support
> -> This is causing issues in the try-fmt v4l2-compliance test
> test VIDIOC_G_FMT: OK
> fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> test VIDIOC_TRY_FMT: FAIL
> fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> test VIDIOC_S_FMT: FAIL
>
> v2: Remove the unicam_{info,debug,error} macros and use
> dev_dbg/dev_err instead.
> ---
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 2 +
> drivers/media/platform/bcm2835/Kconfig | 21 +
> drivers/media/platform/bcm2835/Makefile | 3 +
> .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> 6 files changed, 2850 insertions(+)
> create mode 100644 drivers/media/platform/bcm2835/Kconfig
> create mode 100644 drivers/media/platform/bcm2835/Makefile
> create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c
>
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 9fbdba0fd1e7..033b0358fbb8 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -170,6 +170,7 @@ source "drivers/media/platform/am437x/Kconfig"
> source "drivers/media/platform/xilinx/Kconfig"
> source "drivers/media/platform/rcar-vin/Kconfig"
> source "drivers/media/platform/atmel/Kconfig"
> +source "drivers/media/platform/bcm2835/Kconfig"
> source "drivers/media/platform/sunxi/Kconfig"
>
> config VIDEO_TI_CAL
> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> index 28eb4aadbf45..18894fc586aa 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -86,6 +86,8 @@ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom/camss/
>
> obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
>
> +obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835/
> +
> obj-y += sunxi/
>
> obj-$(CONFIG_VIDEO_MESON_GE2D) += meson/ge2d/
> diff --git a/drivers/media/platform/bcm2835/Kconfig b/drivers/media/platform/bcm2835/Kconfig
> new file mode 100644
> index 000000000000..1691541da905
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/Kconfig
> @@ -0,0 +1,21 @@
> +# Broadcom VideoCore4 V4L2 camera support
> +
> +config VIDEO_BCM2835_UNICAM
> + tristate "Broadcom BCM283x/BCM271x Unicam video capture driver"
> + depends on ARCH_BCM2835 || COMPILE_TEST
> + depends on VIDEO_V4L2
> + select MEDIA_CONTROLLER
> + select VIDEO_V4L2_SUBDEV_API
> + select V4L2_FWNODE
> + select VIDEOBUF2_DMA_CONTIG
> + help
> + Say Y here to enable support for the BCM283x/BCM271x CSI-2 receiver.
> + This is a V4L2 driver that controls the CSI-2 receiver directly,
> + independently from the VC4 firmware.
> + This driver is mutually exclusive with the use of bcm2835-camera. The
> + firmware will disable all access to the peripheral from within the
> + firmware if it finds a DT node using it, and bcm2835-camera will
> + therefore fail to probe.
> +
> + To compile this driver as a module, choose M here. The module will be
> + called bcm2835-unicam.
> diff --git a/drivers/media/platform/bcm2835/Makefile b/drivers/media/platform/bcm2835/Makefile
> new file mode 100644
> index 000000000000..a98aba03598a
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/Makefile
> @@ -0,0 +1,3 @@
> +# Makefile for BCM2835 Unicam driver
> +
> +obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835-unicam.o
> diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> new file mode 100644
> index 000000000000..b8d297076a02
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> @@ -0,0 +1,253 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +
> +/*
> + * Copyright (C) 2017-2020 Raspberry Pi Trading.
> + * Dave Stevenson <[email protected]>
> + */
> +
> +#ifndef VC4_REGS_UNICAM_H
> +#define VC4_REGS_UNICAM_H
> +
> +/*
> + * The following values are taken from files found within the code drop
> + * made by Broadcom for the BCM21553 Graphics Driver, predominantly in
> + * brcm_usrlib/dag/vmcsx/vcinclude/hardware_vc4.h.
> + * They have been modified to be only the register offset.
> + */
> +#define UNICAM_CTRL 0x000
> +#define UNICAM_STA 0x004
> +#define UNICAM_ANA 0x008
> +#define UNICAM_PRI 0x00c
> +#define UNICAM_CLK 0x010
> +#define UNICAM_CLT 0x014
> +#define UNICAM_DAT0 0x018
> +#define UNICAM_DAT1 0x01c
> +#define UNICAM_DAT2 0x020
> +#define UNICAM_DAT3 0x024
> +#define UNICAM_DLT 0x028
> +#define UNICAM_CMP0 0x02c
> +#define UNICAM_CMP1 0x030
> +#define UNICAM_CAP0 0x034
> +#define UNICAM_CAP1 0x038
> +#define UNICAM_ICTL 0x100
> +#define UNICAM_ISTA 0x104
> +#define UNICAM_IDI0 0x108
> +#define UNICAM_IPIPE 0x10c
> +#define UNICAM_IBSA0 0x110
> +#define UNICAM_IBEA0 0x114
> +#define UNICAM_IBLS 0x118
> +#define UNICAM_IBWP 0x11c
> +#define UNICAM_IHWIN 0x120
> +#define UNICAM_IHSTA 0x124
> +#define UNICAM_IVWIN 0x128
> +#define UNICAM_IVSTA 0x12c
> +#define UNICAM_ICC 0x130
> +#define UNICAM_ICS 0x134
> +#define UNICAM_IDC 0x138
> +#define UNICAM_IDPO 0x13c
> +#define UNICAM_IDCA 0x140
> +#define UNICAM_IDCD 0x144
> +#define UNICAM_IDS 0x148
> +#define UNICAM_DCS 0x200
> +#define UNICAM_DBSA0 0x204
> +#define UNICAM_DBEA0 0x208
> +#define UNICAM_DBWP 0x20c
> +#define UNICAM_DBCTL 0x300
> +#define UNICAM_IBSA1 0x304
> +#define UNICAM_IBEA1 0x308
> +#define UNICAM_IDI1 0x30c
> +#define UNICAM_DBSA1 0x310
> +#define UNICAM_DBEA1 0x314
> +#define UNICAM_MISC 0x400
> +
> +/*
> + * The following bitmasks are from the kernel released by Broadcom
> + * for Android - https://android.googlesource.com/kernel/bcm/
> + * The Rhea, Hawaii, and Java chips all contain the same VideoCore4
> + * Unicam block as BCM2835, as defined in eg
> + * arch/arm/mach-rhea/include/mach/rdb_A0/brcm_rdb_cam.h and similar.
> + * Values reworked to use the kernel BIT and GENMASK macros.
> + *
> + * Some of the bit mnenomics have been amended to match the datasheet.
> + */
> +/* UNICAM_CTRL Register */
> +#define UNICAM_CPE BIT(0)
> +#define UNICAM_MEM BIT(1)
> +#define UNICAM_CPR BIT(2)
> +#define UNICAM_CPM_MASK GENMASK(3, 3)
> +#define UNICAM_CPM_CSI2 0
> +#define UNICAM_CPM_CCP2 1
> +#define UNICAM_SOE BIT(4)
> +#define UNICAM_DCM_MASK GENMASK(5, 5)
> +#define UNICAM_DCM_STROBE 0
> +#define UNICAM_DCM_DATA 1
> +#define UNICAM_SLS BIT(6)
> +#define UNICAM_PFT_MASK GENMASK(11, 8)
> +#define UNICAM_OET_MASK GENMASK(20, 12)
> +
> +/* UNICAM_STA Register */
> +#define UNICAM_SYN BIT(0)
> +#define UNICAM_CS BIT(1)
> +#define UNICAM_SBE BIT(2)
> +#define UNICAM_PBE BIT(3)
> +#define UNICAM_HOE BIT(4)
> +#define UNICAM_PLE BIT(5)
> +#define UNICAM_SSC BIT(6)
> +#define UNICAM_CRCE BIT(7)
> +#define UNICAM_OES BIT(8)
> +#define UNICAM_IFO BIT(9)
> +#define UNICAM_OFO BIT(10)
> +#define UNICAM_BFO BIT(11)
> +#define UNICAM_DL BIT(12)
> +#define UNICAM_PS BIT(13)
> +#define UNICAM_IS BIT(14)
> +#define UNICAM_PI0 BIT(15)
> +#define UNICAM_PI1 BIT(16)
> +#define UNICAM_FSI_S BIT(17)
> +#define UNICAM_FEI_S BIT(18)
> +#define UNICAM_LCI_S BIT(19)
> +#define UNICAM_BUF0_RDY BIT(20)
> +#define UNICAM_BUF0_NO BIT(21)
> +#define UNICAM_BUF1_RDY BIT(22)
> +#define UNICAM_BUF1_NO BIT(23)
> +#define UNICAM_DI BIT(24)
> +
> +#define UNICAM_STA_MASK_ALL \
> + (UNICAM_DL | \
> + UNICAM_SBE | \
> + UNICAM_PBE | \
> + UNICAM_HOE | \
> + UNICAM_PLE | \
> + UNICAM_SSC | \
> + UNICAM_CRCE | \
> + UNICAM_IFO | \
> + UNICAM_OFO | \
> + UNICAM_PS | \
> + UNICAM_PI0 | \
> + UNICAM_PI1)
> +
> +/* UNICAM_ANA Register */
> +#define UNICAM_APD BIT(0)
> +#define UNICAM_BPD BIT(1)
> +#define UNICAM_AR BIT(2)
> +#define UNICAM_DDL BIT(3)
> +#define UNICAM_CTATADJ_MASK GENMASK(7, 4)
> +#define UNICAM_PTATADJ_MASK GENMASK(11, 8)
> +
> +/* UNICAM_PRI Register */
> +#define UNICAM_PE BIT(0)
> +#define UNICAM_PT_MASK GENMASK(2, 1)
> +#define UNICAM_NP_MASK GENMASK(7, 4)
> +#define UNICAM_PP_MASK GENMASK(11, 8)
> +#define UNICAM_BS_MASK GENMASK(15, 12)
> +#define UNICAM_BL_MASK GENMASK(17, 16)
> +
> +/* UNICAM_CLK Register */
> +#define UNICAM_CLE BIT(0)
> +#define UNICAM_CLPD BIT(1)
> +#define UNICAM_CLLPE BIT(2)
> +#define UNICAM_CLHSE BIT(3)
> +#define UNICAM_CLTRE BIT(4)
> +#define UNICAM_CLAC_MASK GENMASK(8, 5)
> +#define UNICAM_CLSTE BIT(29)
> +
> +/* UNICAM_CLT Register */
> +#define UNICAM_CLT1_MASK GENMASK(7, 0)
> +#define UNICAM_CLT2_MASK GENMASK(15, 8)
> +
> +/* UNICAM_DATn Registers */
> +#define UNICAM_DLE BIT(0)
> +#define UNICAM_DLPD BIT(1)
> +#define UNICAM_DLLPE BIT(2)
> +#define UNICAM_DLHSE BIT(3)
> +#define UNICAM_DLTRE BIT(4)
> +#define UNICAM_DLSM BIT(5)
> +#define UNICAM_DLFO BIT(28)
> +#define UNICAM_DLSTE BIT(29)
> +
> +#define UNICAM_DAT_MASK_ALL (UNICAM_DLSTE | UNICAM_DLFO)
> +
> +/* UNICAM_DLT Register */
> +#define UNICAM_DLT1_MASK GENMASK(7, 0)
> +#define UNICAM_DLT2_MASK GENMASK(15, 8)
> +#define UNICAM_DLT3_MASK GENMASK(23, 16)
> +
> +/* UNICAM_ICTL Register */
> +#define UNICAM_FSIE BIT(0)
> +#define UNICAM_FEIE BIT(1)
> +#define UNICAM_IBOB BIT(2)
> +#define UNICAM_FCM BIT(3)
> +#define UNICAM_TFC BIT(4)
> +#define UNICAM_LIP_MASK GENMASK(6, 5)
> +#define UNICAM_LCIE_MASK GENMASK(28, 16)
> +
> +/* UNICAM_IDI0/1 Register */
> +#define UNICAM_ID0_MASK GENMASK(7, 0)
> +#define UNICAM_ID1_MASK GENMASK(15, 8)
> +#define UNICAM_ID2_MASK GENMASK(23, 16)
> +#define UNICAM_ID3_MASK GENMASK(31, 24)
> +
> +/* UNICAM_ISTA Register */
> +#define UNICAM_FSI BIT(0)
> +#define UNICAM_FEI BIT(1)
> +#define UNICAM_LCI BIT(2)
> +
> +#define UNICAM_ISTA_MASK_ALL (UNICAM_FSI | UNICAM_FEI | UNICAM_LCI)
> +
> +/* UNICAM_IPIPE Register */
> +#define UNICAM_PUM_MASK GENMASK(2, 0)
> +/* Unpacking modes */
> +#define UNICAM_PUM_NONE 0
> +#define UNICAM_PUM_UNPACK6 1
> +#define UNICAM_PUM_UNPACK7 2
> +#define UNICAM_PUM_UNPACK8 3
> +#define UNICAM_PUM_UNPACK10 4
> +#define UNICAM_PUM_UNPACK12 5
> +#define UNICAM_PUM_UNPACK14 6
> +#define UNICAM_PUM_UNPACK16 7
> +#define UNICAM_DDM_MASK GENMASK(6, 3)
> +#define UNICAM_PPM_MASK GENMASK(9, 7)
> +/* Packing modes */
> +#define UNICAM_PPM_NONE 0
> +#define UNICAM_PPM_PACK8 1
> +#define UNICAM_PPM_PACK10 2
> +#define UNICAM_PPM_PACK12 3
> +#define UNICAM_PPM_PACK14 4
> +#define UNICAM_PPM_PACK16 5
> +#define UNICAM_DEM_MASK GENMASK(11, 10)
> +#define UNICAM_DEBL_MASK GENMASK(14, 12)
> +#define UNICAM_ICM_MASK GENMASK(16, 15)
> +#define UNICAM_IDM_MASK GENMASK(17, 17)
> +
> +/* UNICAM_ICC Register */
> +#define UNICAM_ICFL_MASK GENMASK(4, 0)
> +#define UNICAM_ICFH_MASK GENMASK(9, 5)
> +#define UNICAM_ICST_MASK GENMASK(12, 10)
> +#define UNICAM_ICLT_MASK GENMASK(15, 13)
> +#define UNICAM_ICLL_MASK GENMASK(31, 16)
> +
> +/* UNICAM_DCS Register */
> +#define UNICAM_DIE BIT(0)
> +#define UNICAM_DIM BIT(1)
> +#define UNICAM_DBOB BIT(3)
> +#define UNICAM_FDE BIT(4)
> +#define UNICAM_LDP BIT(5)
> +#define UNICAM_EDL_MASK GENMASK(15, 8)
> +
> +/* UNICAM_DBCTL Register */
> +#define UNICAM_DBEN BIT(0)
> +#define UNICAM_BUF0_IE BIT(1)
> +#define UNICAM_BUF1_IE BIT(2)
> +
> +/* UNICAM_CMP[0,1] register */
> +#define UNICAM_PCE BIT(31)
> +#define UNICAM_GI BIT(9)
> +#define UNICAM_CPH BIT(8)
> +#define UNICAM_PCVC_MASK GENMASK(7, 6)
> +#define UNICAM_PCDT_MASK GENMASK(5, 0)
> +
> +/* UNICAM_MISC register */
> +#define UNICAM_FL0 BIT(6)
> +#define UNICAM_FL1 BIT(9)
> +
> +#endif
> diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam.c b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> new file mode 100644
> index 000000000000..470e691637c7
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> @@ -0,0 +1,2570 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * BCM283x / BCM271x Unicam Capture Driver
> + *
> + * Copyright (C) 2017-2020 - Raspberry Pi (Trading) Ltd.
> + *
> + * Dave Stevenson <[email protected]>
> + *
> + * Based on TI am437x driver by
> + * Benoit Parrot <[email protected]>
> + * Lad, Prabhakar <[email protected]>
> + *
> + * and TI CAL camera interface driver by
> + * Benoit Parrot <[email protected]>
> + *
> + *
> + * There are two camera drivers in the kernel for BCM283x - this one
> + * and bcm2835-camera (currently in staging).
> + *
> + * This driver directly controls the Unicam peripheral - there is no
> + * involvement with the VideoCore firmware. Unicam receives CSI-2 or
> + * CCP2 data and writes it into SDRAM.
> + * The only potential processing options are to repack Bayer data into an
> + * alternate format, and applying windowing.
> + * The repacking does not shift the data, so can repack V4L2_PIX_FMT_Sxxxx10P
> + * to V4L2_PIX_FMT_Sxxxx10, or V4L2_PIX_FMT_Sxxxx12P to V4L2_PIX_FMT_Sxxxx12,
> + * but not generically up to V4L2_PIX_FMT_Sxxxx16. The driver will add both
> + * formats where the relevant formats are defined, and will automatically
> + * configure the repacking as required.
> + * Support for windowing may be added later.
> + *
> + * It should be possible to connect this driver to any sensor with a
> + * suitable output interface and V4L2 subdevice driver.
> + *
> + * bcm2835-camera uses the VideoCore firmware to control the sensor,
> + * Unicam, ISP, and all tuner control loops. Fully processed frames are
> + * delivered to the driver by the firmware. It only has sensor drivers
> + * for Omnivision OV5647, and Sony IMX219 sensors.
> + *
> + * The two drivers are mutually exclusive for the same Unicam instance.
> + * The VideoCore firmware checks the device tree configuration during boot.
> + * If it finds device tree nodes called csi0 or csi1 it will block the
> + * firmware from accessing the peripheral, and bcm2835-camera will
> + * not be able to stream data.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/of_graph.h>
> +#include <linux/pinctrl/consumer.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +#include <linux/videodev2.h>
> +
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-dv-timings.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "bcm2835-unicam-regs.h"
> +
> +#define UNICAM_MODULE_NAME "unicam"
> +
> +/*
> + * Unicam must request a minimum of 250Mhz from the VPU clock.
> + * Otherwise the input FIFOs overrun and cause image corruption.
> + */
> +#define UNICAM_MIN_VPU_CLOCK_RATE (250 * 1000 * 1000)
> +
> +/*
> + * Stride is a 16 bit register, but also has to be a multiple of 32.
> + */
> +#define UNICAM_BPL_ALIGNMENT 32
> +#define UNICAM_MAX_BYTESPERLINE ((1 << 16) - UNICAM_BPL_ALIGNMENT)
> +/*
> + * Max width is therefore determined by the max stride divided by
> + * the number of bits per pixel. Take 32bpp as a
> + * worst case.
> + * No imposed limit on the height, so adopt a square image for want
> + * of anything better.
> + */
> +#define UNICAM_MAX_WIDTH (UNICAM_MAX_BYTESPERLINE / 4)
> +#define UNICAM_MAX_HEIGHT UNICAM_MAX_WIDTH
> +/* Define a nominal minimum image size */
> +#define UNICAM_MIN_WIDTH 16
> +#define UNICAM_MIN_HEIGHT 16
> +/* Default size of the embedded buffer */
> +#define UNICAM_EMBEDDED_SIZE 16384
> +
> +/*
> + * Size of the dummy buffer. Can be any size really, but the DMA
> + * allocation works in units of page sizes.
> + */
> +#define UNICAM_DUMMY_BUF_SIZE PAGE_SIZE
> +
> +#define UNICAM_SD_PAD_SINK 0
> +#define UNICAM_SD_PAD_SOURCE_IMAGE 1
> +#define UNICAM_SD_PAD_SOURCE_METADATA 2
> +#define UNICAM_SD_NUM_PADS (1 + UNICAM_SD_PAD_SOURCE_METADATA)
> +
> +static inline bool unicam_sd_pad_is_sink(u32 pad)
> +{
> + /* Camera RX has 1 sink pad, and N source pads */
> + return pad == 0;
> +}
> +
> +static inline bool unicam_sd_pad_is_source(u32 pad)
> +{
> + /* Camera RX has 1 sink pad, and N source pads */
> + return pad != UNICAM_SD_PAD_SINK;
> +}
> +
> +enum unicam_node_type {
> + UNICAM_IMAGE_NODE,
> + UNICAM_METADATA_NODE,
> + UNICAM_MAX_NODES
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Structure definitions
> + */
> +
> +/*
> + * struct unicam_fmt - Unicam media bus format information
> + * @fourcc: V4L2 pixel format FCC identifier. 0 if n/a.
> + * @unpacked_fourcc: V4L2 pixel format FCC identifier if the data is expanded
> + * out to 16bpp. 0 if n/a.
> + * @code: V4L2 media bus format code.
> + * @depth: Bits per pixel as delivered from the source.
> + * @csi_dt: CSI data type.
> + * @metadata_fmt: This format only applies to the metadata pad.
> + */
> +struct unicam_fmt {
> + u32 fourcc;
> + u32 unpacked_fourcc;
> + u32 code;
> + u8 depth;
> + u8 csi_dt;
> + u8 metadata_fmt:1;
> +};
> +
> +static const struct unicam_fmt unicam_image_formats[] = {
> + /* YUV Formats */
> + {
> + .fourcc = V4L2_PIX_FMT_YUYV,
> + .code = MEDIA_BUS_FMT_YUYV8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + .fourcc = V4L2_PIX_FMT_UYVY,
> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + .fourcc = V4L2_PIX_FMT_YVYU,
> + .code = MEDIA_BUS_FMT_YVYU8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + .fourcc = V4L2_PIX_FMT_VYUY,
> + .code = MEDIA_BUS_FMT_VYUY8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + /* RGB Formats */
> + .fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */
> + .code = MEDIA_BUS_FMT_RGB565_1X16,
> + .depth = 16,
> + .csi_dt = 0x22,
> + }, {
> + .fourcc = V4L2_PIX_FMT_RGB24, /* rgb */
> + .code = MEDIA_BUS_FMT_RGB888_1X24,
> + .depth = 24,
> + .csi_dt = 0x24,

A question for Dave: when using the standard CSI-2 RGB888 format, does
Unicam write RGB24 or BGR24 data to memory ? We have picked
MEDIA_BUS_FMT_BGR888_1X24 as a convention for the CSI-2 RGB888 format,
so I'd like to make sure these two entries are correct.

> + }, {
> + .fourcc = V4L2_PIX_FMT_BGR24, /* bgr */
> + .code = MEDIA_BUS_FMT_BGR888_1X24,
> + .depth = 24,
> + .csi_dt = 0x24,
> + }, {
> + /* Bayer Formats */
> + .fourcc = V4L2_PIX_FMT_SBGGR8,
> + .code = MEDIA_BUS_FMT_SBGGR8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG8,
> + .code = MEDIA_BUS_FMT_SGBRG8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG8,
> + .code = MEDIA_BUS_FMT_SGRBG8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB8,
> + .code = MEDIA_BUS_FMT_SRGGB8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SBGGR10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SBGGR10,
> + .code = MEDIA_BUS_FMT_SBGGR10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGBRG10,
> + .code = MEDIA_BUS_FMT_SGBRG10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGRBG10,
> + .code = MEDIA_BUS_FMT_SGRBG10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SRGGB10,
> + .code = MEDIA_BUS_FMT_SRGGB10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SBGGR12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SBGGR12,
> + .code = MEDIA_BUS_FMT_SBGGR12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGBRG12,
> + .code = MEDIA_BUS_FMT_SGBRG12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGRBG12,
> + .code = MEDIA_BUS_FMT_SGRBG12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SRGGB12,
> + .code = MEDIA_BUS_FMT_SRGGB12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SBGGR14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SBGGR14,
> + .code = MEDIA_BUS_FMT_SBGGR14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGBRG14,
> + .code = MEDIA_BUS_FMT_SGBRG14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGRBG14,
> + .code = MEDIA_BUS_FMT_SGRBG14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SRGGB14,
> + .code = MEDIA_BUS_FMT_SRGGB14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + /* 16 bit Bayer formats could be supported. */
> +
> + /* Greyscale formats */
> + .fourcc = V4L2_PIX_FMT_GREY,
> + .code = MEDIA_BUS_FMT_Y8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_Y10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_Y10,
> + .code = MEDIA_BUS_FMT_Y10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_Y12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_Y12,
> + .code = MEDIA_BUS_FMT_Y12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_Y14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_Y14,
> + .code = MEDIA_BUS_FMT_Y14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + },
> +};
> +
> +static const struct unicam_fmt unicam_meta_formats[] = {
> + {
> + .fourcc = V4L2_META_FMT_8,
> + .code = MEDIA_BUS_FMT_METADATA_8,
> + .depth = 8,
> + .metadata_fmt = 1,
> + }
> +};
> +
> +static const struct v4l2_mbus_framefmt default_image_format = {
> + .width = 640,
> + .height = 480,
> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_SRGB,
> + .ycbcr_enc = V4L2_YCBCR_ENC_601,
> + .quantization = V4L2_QUANTIZATION_LIM_RANGE,
> + .xfer_func = V4L2_XFER_FUNC_SRGB,
> + .flags = 0,
> +};
> +
> +struct unicam_buffer {
> + struct vb2_v4l2_buffer vb;
> + struct list_head list;
> +};
> +
> +static inline struct unicam_buffer *to_unicam_buffer(struct vb2_buffer *vb)
> +{
> + return container_of(vb, struct unicam_buffer, vb.vb2_buf);
> +}
> +
> +struct unicam_node {
> + bool registered;
> + bool streaming;
> + unsigned int id;
> +
> + /* Pointer to the current v4l2_buffer */
> + struct unicam_buffer *cur_frm;
> + /* Pointer to the next v4l2_buffer */
> + struct unicam_buffer *next_frm;
> + /* video capture */
> + const struct unicam_fmt *fmt;
> + /* Used to store current pixel format */
> + struct v4l2_format v_fmt;
> + /* Buffer queue used in video-buf */
> + struct vb2_queue buffer_queue;
> + /* Queue of filled frames */
> + struct list_head dma_queue;
> + /* IRQ lock for DMA queue */
> + spinlock_t dma_queue_lock;
> + /* lock used to access this structure */
> + struct mutex lock;
> + /* Identifies video device for this channel */
> + struct video_device video_dev;
> + /* Pointer to the parent handle */
> + struct unicam_device *dev;
> + struct media_pad pad;
> + /*
> + * Dummy buffer intended to be used by unicam
> + * if we have no other queued buffers to swap to.
> + */
> + void *dummy_buf_cpu_addr;
> + dma_addr_t dummy_buf_dma_addr;
> +};
> +
> +struct unicam_device {
> + struct kref kref;
> +
> + /* peripheral base address */
> + void __iomem *base;
> + /* clock gating base address */
> + void __iomem *clk_gate_base;
> + /* lp clock handle */
> + struct clk *clock;
> + /* vpu clock handle */
> + struct clk *vpu_clock;
> + /* V4l2 device */
> + struct v4l2_device v4l2_dev;
> + struct media_device mdev;
> +
> + /* parent device */
> + struct device *dev;
> + /* subdevice async Notifier */
> + struct v4l2_async_notifier notifier;
> + unsigned int sequence;
> +
> + /* Sensor node */
> + struct {
> + struct v4l2_subdev *subdev;
> + struct media_pad *pad;
> + } sensor;
> +
> + /* Internal subdev */
> + struct {
> + struct v4l2_subdev sd;
> + struct media_pad pads[UNICAM_SD_NUM_PADS];
> + bool streaming;
> + } subdev;
> +
> + enum v4l2_mbus_type bus_type;
> + /*
> + * Stores bus.mipi_csi2.flags for CSI2 sensors, or
> + * bus.mipi_csi1.strobe for CCP2.
> + */
> + unsigned int bus_flags;
> + unsigned int max_data_lanes;
> + unsigned int active_data_lanes;
> +
> + struct media_pipeline pipe;
> +
> + struct unicam_node node[UNICAM_MAX_NODES];
> +};
> +
> +static inline struct unicam_device *
> +notifier_to_unicam_device(struct v4l2_async_notifier *notifier)
> +{
> + return container_of(notifier, struct unicam_device, notifier);
> +}
> +
> +static inline struct unicam_device *
> +sd_to_unicam_device(struct v4l2_subdev *sd)
> +{
> + return container_of(sd, struct unicam_device, subdev.sd);
> +}
> +
> +static void unicam_release(struct kref *kref)
> +{
> + struct unicam_device *unicam =
> + container_of(kref, struct unicam_device, kref);
> +
> + if (unicam->mdev.dev)
> + media_device_cleanup(&unicam->mdev);
> +
> + kfree(unicam);
> +}
> +
> +static void unicam_put(struct unicam_device *unicam)
> +{
> + kref_put(&unicam->kref, unicam_release);
> +}
> +
> +static struct unicam_device *unicam_get(struct unicam_device *unicam)
> +{
> + kref_get(&unicam->kref);
> + return unicam;
> +}
> +
> +static void unicam_node_release(struct video_device *vdev)
> +{
> + struct unicam_node *node = video_get_drvdata(vdev);
> +
> + unicam_put(node->dev);
> +}

Thanks for moving the get/put functions here. unicam_node_release(),
however, I would keep just before unicam_register_node(), it logically
belongs there.

> +
> +/* -----------------------------------------------------------------------------
> + * Misc helper functions
> + */
> +
> +static inline bool is_metadata_node(struct unicam_node *node)
> +{
> + return node->video_dev.device_caps & V4L2_CAP_META_CAPTURE;

return node->id == UNICAM_METADATA_NODE;

is simpler.

> +}
> +
> +static inline bool is_image_node(struct unicam_node *node)
> +{
> + return node->video_dev.device_caps & V4L2_CAP_VIDEO_CAPTURE;

return node->id == UNICAM_IMAGE_NODE;

You could even replace

if (is_image_node(node))

with

if (node->id == UNICAM_IMAGE_NODE)

in the code if you prefer.

> +}
> +
> +/* Hardware access */
> +static inline void unicam_clk_write(struct unicam_device *unicam, u32 val)
> +{
> + /* Pass the CM_PASSWORD along with the value. */
> + writel(val | 0x5a000000, unicam->clk_gate_base);
> +}
> +
> +static inline u32 unicam_reg_read(struct unicam_device *unicam, u32 offset)
> +{
> + return readl(unicam->base + offset);
> +}
> +
> +static inline void unicam_reg_write(struct unicam_device *unicam, u32 offset, u32 val)
> +{
> + writel(val, unicam->base + offset);
> +}
> +
> +static inline int unicam_get_field(u32 value, u32 mask)
> +{
> + return (value & mask) >> __ffs(mask);
> +}
> +
> +static inline void unicam_set_field(u32 *valp, u32 field, u32 mask)
> +{
> + u32 val = *valp;
> +
> + val &= ~mask;
> + val |= (field << __ffs(mask)) & mask;
> + *valp = val;
> +}
> +
> +static inline void unicam_reg_write_field(struct unicam_device *unicam, u32 offset,
> + u32 field, u32 mask)
> +{
> + u32 val = unicam_reg_read(unicam, offset);
> +
> + unicam_set_field(&val, field, mask);
> + unicam_reg_write(unicam, offset, val);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Format data table and helper functions
> + */
> +
> +/* Format setup functions */
> +static const struct unicam_fmt *unicam_find_format_by_code(u32 code, u32 pad)
> +{
> + const struct unicam_fmt *formats;
> + unsigned int num_formats;
> + unsigned int i;
> +
> + if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + formats = unicam_image_formats;
> + num_formats = ARRAY_SIZE(unicam_image_formats);
> + } else {
> + formats = unicam_meta_formats;
> + num_formats = ARRAY_SIZE(unicam_meta_formats);
> + }
> +
> + for (i = 0; i < num_formats; i++) {
> + if (formats[i].code == code)
> + return &formats[i];
> + }
> +
> + return NULL;
> +}
> +
> +static const struct unicam_fmt *unicam_find_format_by_fourcc(u32 fourcc, u32 pad)

As this function is only used in contexts where you have a video node, I
would replace the pad parameter with the node:

static const struct unicam_fmt *
unicam_find_format_by_fourcc(struct unicam_node *node, u32 fourcc)

and use is_image_node() to replace the pad check.

> +{
> + const struct unicam_fmt *formats;
> + unsigned int num_formats;
> + unsigned int i;
> +
> + if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + formats = unicam_image_formats;
> + num_formats = ARRAY_SIZE(unicam_image_formats);
> + } else {
> + formats = unicam_meta_formats;
> + num_formats = ARRAY_SIZE(unicam_meta_formats);
> + }
> +
> + for (i = 0; i < num_formats; ++i) {
> + if (formats[i].fourcc == fourcc)
> + return &formats[i];
> + }
> +
> + return NULL;
> +}
> +
> +static int unicam_calc_format_size_bpl(struct unicam_device *unicam,
> + const struct unicam_fmt *fmt,
> + struct v4l2_format *f)
> +{
> + u32 min_bpl;
> +
> + v4l_bound_align_image(&f->fmt.pix.width, UNICAM_MIN_WIDTH,
> + UNICAM_MAX_WIDTH, 2,
> + &f->fmt.pix.height, UNICAM_MIN_HEIGHT,
> + UNICAM_MAX_HEIGHT, 0, 0);
> +
> + /* Unpacking always goes to 16bpp */
> + if (f->fmt.pix.pixelformat == fmt->unpacked_fourcc)
> + min_bpl = f->fmt.pix.width * 2;
> + else
> + min_bpl = f->fmt.pix.width * fmt->depth / 8;
> + min_bpl = ALIGN(min_bpl, UNICAM_BPL_ALIGNMENT);
> +
> + f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline,
> + UNICAM_BPL_ALIGNMENT);
> + f->fmt.pix.bytesperline = clamp_t(unsigned int,
> + f->fmt.pix.bytesperline,
> + min_bpl,
> + UNICAM_MAX_BYTESPERLINE);

f->fmt.pix.bytesperline = clamp_t(unsigned int, f->fmt.pix.bytesperline,
min_bpl, UNICAM_MAX_BYTESPERLINE);

is still withing 80 columns :-)

> +
> + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
> +
> + dev_dbg(unicam->dev, "%s: fourcc: %08X size: %ux%u bpl:%u img_size:%u\n",

We now have a nice printk format specifier to print 4CCs:

s/%08X/%p4cc/

> + __func__,
> + f->fmt.pix.pixelformat,

And here,

&f->fmt.pix.pixelformat,

> + f->fmt.pix.width, f->fmt.pix.height,
> + f->fmt.pix.bytesperline, f->fmt.pix.sizeimage);
> +
> + return 0;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Hardware handling
> + */
> +
> +static void unicam_wr_dma_addr(struct unicam_node *node, dma_addr_t dmaaddr,
> + unsigned int buffer_size)
> +{
> + dma_addr_t endaddr = dmaaddr + buffer_size;
> +
> + if (node->id == UNICAM_IMAGE_NODE) {
> + unicam_reg_write(node->dev, UNICAM_IBSA0, dmaaddr);
> + unicam_reg_write(node->dev, UNICAM_IBEA0, endaddr);
> + } else {
> + unicam_reg_write(node->dev, UNICAM_DBSA0, dmaaddr);
> + unicam_reg_write(node->dev, UNICAM_DBEA0, endaddr);
> + }
> +}
> +
> +static unsigned int unicam_get_lines_done(struct unicam_device *unicam)
> +{
> + dma_addr_t start_addr, cur_addr;
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> + unsigned int stride = node->v_fmt.fmt.pix.bytesperline;
> + struct unicam_buffer *frm = node->cur_frm;
> +
> + if (!frm)
> + return 0;
> +
> + start_addr = vb2_dma_contig_plane_dma_addr(&frm->vb.vb2_buf, 0);
> + cur_addr = unicam_reg_read(unicam, UNICAM_IBWP);
> + return (unsigned int)(cur_addr - start_addr) / stride;
> +}
> +
> +static void unicam_schedule_next_buffer(struct unicam_node *node)
> +{
> + struct unicam_buffer *buf;
> + unsigned int size;
> + dma_addr_t addr;
> +
> + buf = list_first_entry(&node->dma_queue, struct unicam_buffer, list);
> + node->next_frm = buf;
> + list_del(&buf->list);
> +
> + addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> + if (is_image_node(node))
> + size = node->v_fmt.fmt.pix.sizeimage;
> + else
> + size = node->v_fmt.fmt.meta.buffersize;
> +
> + unicam_wr_dma_addr(node, addr, size);
> +}
> +
> +static void unicam_schedule_dummy_buffer(struct unicam_node *node)
> +{
> + struct unicam_device *unicam = node->dev;
> + int node_id = is_image_node(node) ? UNICAM_IMAGE_NODE : UNICAM_METADATA_NODE;
> +
> + dev_dbg(unicam->dev, "Scheduling dummy buffer for node %d\n", node_id);

s/node_id/node->id/ and drop the local variable.

> +
> + unicam_wr_dma_addr(node, node->dummy_buf_dma_addr,
> + UNICAM_DUMMY_BUF_SIZE);
> +
> + node->next_frm = NULL;
> +}
> +
> +static void unicam_process_buffer_complete(struct unicam_node *node,
> + unsigned int sequence)
> +{
> + node->cur_frm->vb.field = node->v_fmt.fmt.pix.field;
> + node->cur_frm->vb.sequence = sequence;
> +
> + vb2_buffer_done(&node->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +}
> +
> +static void unicam_queue_event_sof(struct unicam_device *unicam)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> +
> + struct v4l2_event event = {
> + .type = V4L2_EVENT_FRAME_SYNC,
> + .u.frame_sync.frame_sequence = unicam->sequence,
> + };
> +
> + v4l2_event_queue(&node->video_dev, &event);
> +}
> +
> +/*
> + * unicam_isr : ISR handler for unicam capture
> + * @irq: irq number
> + * @dev_id: dev_id ptr
> + *
> + * It changes status of the captured buffer, takes next buffer from the queue
> + * and sets its address in unicam registers
> + */
> +static irqreturn_t unicam_isr(int irq, void *dev)
> +{
> + struct unicam_device *unicam = dev;
> + unsigned int lines_done = unicam_get_lines_done(dev);
> + unsigned int sequence = unicam->sequence;
> + unsigned int i;
> + u32 ista, sta;
> + bool fe;
> + u64 ts;
> +
> + sta = unicam_reg_read(unicam, UNICAM_STA);
> + /* Write value back to clear the interrupts */
> + unicam_reg_write(unicam, UNICAM_STA, sta);
> +
> + ista = unicam_reg_read(unicam, UNICAM_ISTA);
> + /* Write value back to clear the interrupts */
> + unicam_reg_write(unicam, UNICAM_ISTA, ista);
> +
> + dev_dbg(unicam->dev, "ISR: ISTA: 0x%X, STA: 0x%X, sequence %d, lines done %d",
> + ista, sta, sequence, lines_done);
> +
> + if (!(sta & (UNICAM_IS | UNICAM_PI0)))
> + return IRQ_HANDLED;
> +
> + /*
> + * Look for either the Frame End interrupt or the Packet Capture status
> + * to signal a frame end.
> + */
> + fe = ista & UNICAM_FEI || sta & UNICAM_PI0;
> +
> + /*
> + * We must run the frame end handler first. If we have a valid next_frm
> + * and we get a simultaneout FE + FS interrupt, running the FS handler
> + * first would null out the next_frm ptr and we would have lost the
> + * buffer forever.
> + */
> + if (fe) {
> + /*
> + * Ensure we have swapped buffers already as we can't
> + * stop the peripheral. If no buffer is available, use a
> + * dummy buffer to dump out frames until we get a new buffer
> + * to use.
> + */
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + if (!unicam->node[i].streaming)
> + continue;
> +
> + /*
> + * If cur_frm == next_frm, it means we have not had
> + * a chance to swap buffers, likely due to having
> + * multiple interrupts occurring simultaneously (like FE
> + * + FS + LS). In this case, we cannot signal the buffer
> + * as complete, as the HW will reuse that buffer.
> + */
> + if (unicam->node[i].cur_frm &&
> + unicam->node[i].cur_frm != unicam->node[i].next_frm)
> + unicam_process_buffer_complete(&unicam->node[i],
> + sequence);
> + unicam->node[i].cur_frm = unicam->node[i].next_frm;
> + }
> + unicam->sequence++;
> + }
> +
> + if (ista & UNICAM_FSI) {
> + /*
> + * Timestamp is to be when the first data byte was captured,
> + * aka frame start.
> + */
> + ts = ktime_get_ns();
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + if (!unicam->node[i].streaming)
> + continue;
> +
> + if (unicam->node[i].cur_frm)
> + unicam->node[i].cur_frm->vb.vb2_buf.timestamp =
> + ts;
> + else
> + dev_dbg(unicam->v4l2_dev.dev,
> + "ISR: [%d] Dropping frame, buffer not available at FS\n",
> + i);
> + /*
> + * Set the next frame output to go to a dummy frame
> + * if we have not managed to obtain another frame
> + * from the queue.
> + */
> + unicam_schedule_dummy_buffer(&unicam->node[i]);
> + }
> +
> + unicam_queue_event_sof(unicam);
> + }
> +
> + /*
> + * Cannot swap buffer at frame end, there may be a race condition
> + * where the HW does not actually swap it if the new frame has
> + * already started.
> + */
> + if (ista & (UNICAM_FSI | UNICAM_LCI) && !fe) {
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + if (!unicam->node[i].streaming)
> + continue;
> +
> + spin_lock(&unicam->node[i].dma_queue_lock);
> + if (!list_empty(&unicam->node[i].dma_queue) &&
> + !unicam->node[i].next_frm)
> + unicam_schedule_next_buffer(&unicam->node[i]);
> + spin_unlock(&unicam->node[i].dma_queue_lock);
> + }
> + }
> +
> + if (unicam_reg_read(unicam, UNICAM_ICTL) & UNICAM_FCM) {
> + /* Switch out of trigger mode if selected */
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_TFC);
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 0, UNICAM_FCM);
> + }
> + return IRQ_HANDLED;
> +}
> +
> +static void unicam_set_packing_config(struct unicam_device *unicam)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> + u32 pack, unpack;
> + u32 val;
> +
> + if (node->v_fmt.fmt.pix.pixelformat ==
> + node->fmt->fourcc) {

This holds on a single line.

> + unpack = UNICAM_PUM_NONE;
> + pack = UNICAM_PPM_NONE;
> + } else {
> + switch (node->fmt->depth) {
> + case 8:
> + unpack = UNICAM_PUM_UNPACK8;
> + break;
> + case 10:
> + unpack = UNICAM_PUM_UNPACK10;
> + break;
> + case 12:
> + unpack = UNICAM_PUM_UNPACK12;
> + break;
> + case 14:
> + unpack = UNICAM_PUM_UNPACK14;
> + break;
> + case 16:
> + unpack = UNICAM_PUM_UNPACK16;
> + break;
> + default:
> + unpack = UNICAM_PUM_NONE;
> + break;
> + }
> +
> + /* Repacking is always to 16bpp */
> + pack = UNICAM_PPM_PACK16;
> + }
> +
> + val = 0;
> + unicam_set_field(&val, unpack, UNICAM_PUM_MASK);
> + unicam_set_field(&val, pack, UNICAM_PPM_MASK);
> + unicam_reg_write(unicam, UNICAM_IPIPE, val);
> +}
> +
> +static void unicam_cfg_image_id(struct unicam_device *unicam)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> +
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + /* CSI2 mode, hardcode VC 0 for now. */
> + unicam_reg_write(unicam, UNICAM_IDI0,
> + (0 << 6) | node->fmt->csi_dt);
> + } else {
> + /* CCP2 mode */
> + unicam_reg_write(unicam, UNICAM_IDI0,
> + 0x80 | node->fmt->csi_dt);
> + }
> +}
> +
> +static void unicam_enable_ed(struct unicam_device *unicam)
> +{
> + u32 val = unicam_reg_read(unicam, UNICAM_DCS);
> +
> + unicam_set_field(&val, 2, UNICAM_EDL_MASK);
> + /* Do not wrap at the end of the embedded data buffer */
> + unicam_set_field(&val, 0, UNICAM_DBOB);
> +
> + unicam_reg_write(unicam, UNICAM_DCS, val);
> +}
> +
> +static void unicam_start_rx(struct unicam_device *unicam, dma_addr_t *addr)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> + int line_int_freq = node->v_fmt.fmt.pix.height >> 2;
> + unsigned int size, i;
> + u32 val;
> +
> + if (line_int_freq < 128)
> + line_int_freq = 128;
> +
> + /* Enable lane clocks */
> + val = 1;
> + for (i = 0; i < unicam->active_data_lanes; i++)
> + val = val << 2 | 1;
> + unicam_clk_write(unicam, val);
> +
> + /* Basic init */
> + unicam_reg_write(unicam, UNICAM_CTRL, UNICAM_MEM);
> +
> + /* Enable analogue control, and leave in reset. */
> + val = UNICAM_AR;
> + unicam_set_field(&val, 7, UNICAM_CTATADJ_MASK);
> + unicam_set_field(&val, 7, UNICAM_PTATADJ_MASK);
> + unicam_reg_write(unicam, UNICAM_ANA, val);
> + usleep_range(1000, 2000);
> +
> + /* Come out of reset */
> + unicam_reg_write_field(unicam, UNICAM_ANA, 0, UNICAM_AR);
> +
> + /* Peripheral reset */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPR);
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPR);
> +
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPE);
> +
> + /* Enable Rx control. */
> + val = unicam_reg_read(unicam, UNICAM_CTRL);
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + unicam_set_field(&val, UNICAM_CPM_CSI2, UNICAM_CPM_MASK);
> + unicam_set_field(&val, UNICAM_DCM_STROBE, UNICAM_DCM_MASK);
> + } else {
> + unicam_set_field(&val, UNICAM_CPM_CCP2, UNICAM_CPM_MASK);
> + unicam_set_field(&val, unicam->bus_flags, UNICAM_DCM_MASK);
> + }
> + /* Packet framer timeout */
> + unicam_set_field(&val, 0xf, UNICAM_PFT_MASK);
> + unicam_set_field(&val, 128, UNICAM_OET_MASK);
> + unicam_reg_write(unicam, UNICAM_CTRL, val);
> +
> + unicam_reg_write(unicam, UNICAM_IHWIN, 0);
> + unicam_reg_write(unicam, UNICAM_IVWIN, 0);
> +
> + /* AXI bus access QoS setup */
> + val = unicam_reg_read(unicam, UNICAM_PRI);
> + unicam_set_field(&val, 0, UNICAM_BL_MASK);
> + unicam_set_field(&val, 0, UNICAM_BS_MASK);
> + unicam_set_field(&val, 0xe, UNICAM_PP_MASK);
> + unicam_set_field(&val, 8, UNICAM_NP_MASK);
> + unicam_set_field(&val, 2, UNICAM_PT_MASK);
> + unicam_set_field(&val, 1, UNICAM_PE);
> + unicam_reg_write(unicam, UNICAM_PRI, val);
> +
> + unicam_reg_write_field(unicam, UNICAM_ANA, 0, UNICAM_DDL);
> +
> + /* Always start in trigger frame capture mode (UNICAM_FCM set) */
> + val = UNICAM_FSIE | UNICAM_FEIE | UNICAM_FCM | UNICAM_IBOB;
> + unicam_set_field(&val, line_int_freq, UNICAM_LCIE_MASK);
> + unicam_reg_write(unicam, UNICAM_ICTL, val);
> + unicam_reg_write(unicam, UNICAM_STA, UNICAM_STA_MASK_ALL);
> + unicam_reg_write(unicam, UNICAM_ISTA, UNICAM_ISTA_MASK_ALL);
> +
> + /* tclk_term_en */
> + unicam_reg_write_field(unicam, UNICAM_CLT, 2, UNICAM_CLT1_MASK);
> + /* tclk_settle */
> + unicam_reg_write_field(unicam, UNICAM_CLT, 6, UNICAM_CLT2_MASK);
> + /* td_term_en */
> + unicam_reg_write_field(unicam, UNICAM_DLT, 2, UNICAM_DLT1_MASK);
> + /* ths_settle */
> + unicam_reg_write_field(unicam, UNICAM_DLT, 6, UNICAM_DLT2_MASK);
> + /* trx_enable */
> + unicam_reg_write_field(unicam, UNICAM_DLT, 0, UNICAM_DLT3_MASK);
> +
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_SOE);
> +
> + /* Packet compare setup - required to avoid missing frame ends */
> + val = 0;
> + unicam_set_field(&val, 1, UNICAM_PCE);
> + unicam_set_field(&val, 1, UNICAM_GI);
> + unicam_set_field(&val, 1, UNICAM_CPH);
> + unicam_set_field(&val, 0, UNICAM_PCVC_MASK);
> + unicam_set_field(&val, 1, UNICAM_PCDT_MASK);
> + unicam_reg_write(unicam, UNICAM_CMP0, val);
> +
> + /* Enable clock lane and set up terminations */
> + val = 0;
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + /* CSI2 */
> + unicam_set_field(&val, 1, UNICAM_CLE);
> + unicam_set_field(&val, 1, UNICAM_CLLPE);
> + if (unicam->bus_flags & V4L2_MBUS_CSI2_CONTINUOUS_CLOCK) {
> + unicam_set_field(&val, 1, UNICAM_CLTRE);
> + unicam_set_field(&val, 1, UNICAM_CLHSE);
> + }
> + } else {
> + /* CCP2 */
> + unicam_set_field(&val, 1, UNICAM_CLE);
> + unicam_set_field(&val, 1, UNICAM_CLHSE);
> + unicam_set_field(&val, 1, UNICAM_CLTRE);
> + }
> + unicam_reg_write(unicam, UNICAM_CLK, val);
> +
> + /*
> + * Enable required data lanes with appropriate terminations.
> + * The same value needs to be written to UNICAM_DATn registers for
> + * the active lanes, and 0 for inactive ones.
> + */
> + val = 0;
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + /* CSI2 */
> + unicam_set_field(&val, 1, UNICAM_DLE);
> + unicam_set_field(&val, 1, UNICAM_DLLPE);
> + if (unicam->bus_flags & V4L2_MBUS_CSI2_CONTINUOUS_CLOCK) {
> + unicam_set_field(&val, 1, UNICAM_DLTRE);
> + unicam_set_field(&val, 1, UNICAM_DLHSE);
> + }
> + } else {
> + /* CCP2 */
> + unicam_set_field(&val, 1, UNICAM_DLE);
> + unicam_set_field(&val, 1, UNICAM_DLHSE);
> + unicam_set_field(&val, 1, UNICAM_DLTRE);
> + }
> + unicam_reg_write(unicam, UNICAM_DAT0, val);
> +
> + if (unicam->active_data_lanes == 1)
> + val = 0;
> + unicam_reg_write(unicam, UNICAM_DAT1, val);
> +
> + if (unicam->max_data_lanes > 2) {
> + /*
> + * Registers UNICAM_DAT2 and UNICAM_DAT3 only valid if the
> + * instance supports more than 2 data lanes.
> + */
> + if (unicam->active_data_lanes == 2)
> + val = 0;
> + unicam_reg_write(unicam, UNICAM_DAT2, val);
> +
> + if (unicam->active_data_lanes == 3)
> + val = 0;
> + unicam_reg_write(unicam, UNICAM_DAT3, val);
> + }
> +
> + unicam_reg_write(unicam, UNICAM_IBLS,
> + node->v_fmt.fmt.pix.bytesperline);

This holds on a single line too.

> + size = node->v_fmt.fmt.pix.sizeimage;
> + unicam_wr_dma_addr(&unicam->node[UNICAM_IMAGE_NODE],
> + addr[UNICAM_IMAGE_NODE], size);
> + unicam_set_packing_config(unicam);
> + unicam_cfg_image_id(unicam);
> +
> + val = unicam_reg_read(unicam, UNICAM_MISC);
> + unicam_set_field(&val, 1, UNICAM_FL0);
> + unicam_set_field(&val, 1, UNICAM_FL1);
> + unicam_reg_write(unicam, UNICAM_MISC, val);
> +
> + /* Enable peripheral */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPE);
> +
> + /* Load image pointers */
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_LIP_MASK);
> +
> + /*
> + * Enable trigger only for the first frame to
> + * sync correctly to the FS from the source.
> + */
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_TFC);
> +}
> +
> +static void unicam_start_metadata(struct unicam_device *unicam, dma_addr_t *addr)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_METADATA_NODE];
> + unsigned int size;
> +
> + dev_dbg(unicam->dev, "enable metadata dma\n");
> + size = node->v_fmt.fmt.meta.buffersize;
> + unicam_enable_ed(unicam);
> + unicam_wr_dma_addr(node, addr[UNICAM_METADATA_NODE], size);
> + unicam_reg_write_field(unicam, UNICAM_DCS, 1, UNICAM_LDP);
> +}
> +
> +static void unicam_disable(struct unicam_device *unicam)
> +{
> + /* Analogue lane control disable */
> + unicam_reg_write_field(unicam, UNICAM_ANA, 1, UNICAM_DDL);
> +
> + /* Stop the output engine */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_SOE);
> +
> + /* Disable the data lanes. */
> + unicam_reg_write(unicam, UNICAM_DAT0, 0);
> + unicam_reg_write(unicam, UNICAM_DAT1, 0);
> +
> + if (unicam->max_data_lanes > 2) {
> + unicam_reg_write(unicam, UNICAM_DAT2, 0);
> + unicam_reg_write(unicam, UNICAM_DAT3, 0);
> + }
> +
> + /* Peripheral reset */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPR);
> + usleep_range(50, 100);
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPR);
> +
> + /* Disable peripheral */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPE);
> +
> + /* Clear ED setup */
> + unicam_reg_write(unicam, UNICAM_DCS, 0);
> +
> + /* Disable all lane clocks */
> + unicam_clk_write(unicam, 0);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 subdev Operations
> + */
> +
> +static int __unicam_subdev_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_krouting *routing)
> +{
> + int ret;
> +
> + ret = v4l2_subdev_routing_validate_1_to_1(routing);
> + if (ret)
> + return ret;
> +
> + v4l2_subdev_lock_state(state);

As explained in the review ov the imx219 patches, v11 of the V4L2
streams series will handle locking in the core, which should simplify
the driver.

> +
> + ret = v4l2_subdev_set_routing(sd, state, routing);

I understand that you can't use v4l2_subdev_set_routing_with_fmt() as
the formats for the image and metadata streams are not the same, but you
still need to initialize formats (manually) to default values after
setting routing.

> +
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_subdev_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + enum v4l2_subdev_format_whence which,
> + struct v4l2_subdev_krouting *routing)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> +
> + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && unicam->subdev.streaming)
> + return -EBUSY;
> +
> + return __unicam_subdev_set_routing(sd, state, routing);
> +}
> +
> +static int unicam_subdev_init_cfg(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state)
> +{
> + struct v4l2_subdev_route routes[] = {
> + {
> + .sink_pad = UNICAM_SD_PAD_SINK,
> + .sink_stream = 0,
> + .source_pad = UNICAM_SD_PAD_SOURCE_IMAGE,
> + .source_stream = 0,
> + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> + },
> + };
> +
> + struct v4l2_subdev_krouting routing = {
> + .num_routes = ARRAY_SIZE(routes),
> + .routes = routes,
> + };
> +
> + /* Initialize routing to single route to the fist source pad */
> + return __unicam_subdev_set_routing(sd, state, &routing);
> +}
> +
> +static int unicam_subdev_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + u32 pad, stream;
> + int ret = -EINVAL;
> +
> + v4l2_subdev_lock_state(state);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, code->pad,
> + code->stream, &pad,
> + &stream);
> + if (ret)
> + goto out;
> +
> + if (unicam_sd_pad_is_source(code->pad)) {
> + /* No transcoding, source and sink codes must match. */
> + struct v4l2_mbus_framefmt *fmt;
> +
> + fmt = v4l2_state_get_stream_format(state, pad, stream);
> + if (!fmt)
> + goto out;
> +
> + if (code->index > 0)
> + goto out;
> +
> + code->code = fmt->code;
> + } else {
> + const struct unicam_fmt *formats;
> + unsigned int num_formats;
> +
> + if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + formats = unicam_image_formats;
> + num_formats = ARRAY_SIZE(unicam_image_formats);
> + } else {
> + formats = unicam_meta_formats;
> + num_formats = ARRAY_SIZE(unicam_meta_formats);
> + }
> +
> + if (code->index >= num_formats)
> + goto out;
> +
> + code->code = formats[code->index].code;
> + }
> +
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_sd_enable_streams(struct v4l2_subdev *sd, u32 pad,
> + u64 streams_mask)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_subdev_state *state;
> + u32 other_pad, other_stream;
> + int ret;
> +
> + if (WARN_ON(streams_mask != 1))
> + return -EINVAL;
> +
> + state = v4l2_subdev_lock_active_state(sd);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
> + &other_pad, &other_stream);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + if (ret)
> + return ret;
> +
> + unicam->sequence = 0;
> +
> + dev_dbg(unicam->dev, "Running with %u data lanes\n",
> + unicam->active_data_lanes);
> +
> + ret = clk_set_min_rate(unicam->vpu_clock, UNICAM_MIN_VPU_CLOCK_RATE);
> + if (ret) {
> + dev_err(unicam->dev, "failed to set up VPU clock\n");
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(unicam->vpu_clock);
> + if (ret) {
> + dev_err(unicam->dev, "Failed to enable VPU clock: %d\n", ret);
> + goto err_vpu_clock;
> + }
> +
> + ret = clk_set_rate(unicam->clock, 100 * 1000 * 1000);
> + if (ret) {
> + dev_err(unicam->dev, "failed to set up CSI clock\n");
> + goto err_vpu_prepare;
> + }
> +
> + ret = clk_prepare_enable(unicam->clock);
> + if (ret) {
> + dev_err(unicam->dev, "Failed to enable CSI clock: %d\n", ret);
> + goto err_vpu_prepare;
> + }
> +
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, enable_streams,
> + unicam->sensor.pad->index, BIT(other_stream));

In v11 of the V4L2 streams series there will be a
v4l2_subdev_enable_streams() helper to replace this, which will handle
backward-compatibility with .s_stream(), so you'll be able to drop the
manual .s_stream() handling.

> +
> + if (ret && ret == -ENOIOCTLCMD)
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, s_stream, 1);
> +
> + if (ret) {
> + dev_err(unicam->dev, "stream on failed in subdev\n");
> + goto err_enable_stream;
> + }
> +
> + unicam->subdev.streaming = true;
> +
> + return 0;
> +
> +err_enable_stream:

Labels should be named based on what they do, not where they come from.

err_clk:

> + clk_disable_unprepare(unicam->clock);
> +err_vpu_prepare:

err_vpu_clk:

> + clk_disable_unprepare(unicam->vpu_clock);
> +err_vpu_clock:

err_clk_rate:

> + if (clk_set_min_rate(unicam->vpu_clock, 0))
> + dev_err(unicam->dev, "failed to reset the VPU clock\n");
> + return ret;
> +}
> +
> +static int unicam_sd_disable_streams(struct v4l2_subdev *sd, u32 pad,
> + u64 streams_mask)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_subdev_state *state;
> + u32 other_pad, other_stream;
> + int ret;
> +
> + if (WARN_ON(streams_mask != 1))
> + return -EINVAL;
> +
> + state = v4l2_subdev_lock_active_state(sd);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
> + &other_pad, &other_stream);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + if (ret) {
> + dev_err(unicam->dev, "disable streams failed: %d\n", ret);
> + return ret;
> + }
> +
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, disable_streams,
> + unicam->sensor.pad->index, BIT(other_stream));
> +
> + if (ret && ret == -ENOIOCTLCMD) {
> + /* The sensor does not support disable streams. */
> + if (unicam->node[UNICAM_IMAGE_NODE].streaming &&
> + other_pad == unicam->node[UNICAM_METADATA_NODE].pad.index) {
> + /* We won't stop the sensor yet. */
> + unicam->subdev.streaming = false;
> + return 0;
> + }
> + /* There is no more the metadata node, we can stop. */
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, s_stream, 0);
> + }
> +
> + clk_disable_unprepare(unicam->clock);
> + if (clk_set_min_rate(unicam->vpu_clock, 0))
> + dev_err(unicam->dev, "failed to reset the VPU clock\n");
> + clk_disable_unprepare(unicam->vpu_clock);
> +
> + unicam->subdev.streaming = false;

I think you'll need to refactor the enable and disable streams
implementation. You set the minimum clock rate to 0 and set streaming to
false when any stream is stopped. This means that stopping capture of
metadata while image data capture is still on will cause issues.

> +
> + return 0;
> +}
> +
> +static int unicam_subdev_set_pad_format(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_format *format)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_mbus_framefmt *sink_format, *source_format;
> + const struct unicam_fmt *fmtinfo;
> + int ret = 0;
> +
> + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && unicam->subdev.streaming)
> + return -EBUSY;
> +
> + /* No transcoding, source and sink formats must match. */
> + if (unicam_sd_pad_is_source(format->pad))
> + return v4l2_subdev_get_fmt(sd, state, format);
> +
> + fmtinfo = unicam_find_format_by_code(format->format.code, format->pad);
> + if (!fmtinfo)
> + fmtinfo = &unicam_image_formats[0];

That's not right for the metadata *stream*.

> +
> + if (format->pad == UNICAM_SD_PAD_SOURCE_IMAGE) {

This can't happen as you return early for source pads. You need to check
the stream here, not the pad.

> + format->format.width = clamp_t(unsigned int,
> + format->format.width,
> + UNICAM_MIN_WIDTH,
> + UNICAM_MAX_WIDTH);
> + format->format.height = clamp_t(unsigned int,
> + format->format.height,
> + UNICAM_MIN_HEIGHT,
> + UNICAM_MAX_HEIGHT);
> + }
> + format->format.field = V4L2_FIELD_NONE;
> +
> + v4l2_subdev_lock_state(state);
> +
> + sink_format = v4l2_state_get_stream_format(state, format->pad,
> + format->stream);
> + source_format = v4l2_subdev_state_get_opposite_stream_format(state,
> + format->pad,
> + format->stream);
> + if (!sink_format || !source_format) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + *sink_format = format->format;
> + *source_format = format->format;
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_subdev_enum_frame_size(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_frame_size_enum *fse)
> +{
> + const struct unicam_fmt *fmtinfo;
> + u32 pad, stream;
> + int ret = -EINVAL;
> +
> + if (fse->index > 0)
> + return ret;
> +
> + v4l2_subdev_lock_state(state);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, fse->pad,
> + fse->stream, &pad,
> + &stream);
> + if (ret)
> + goto out;
> +
> + if (unicam_sd_pad_is_source(fse->pad)) {
> + /* No transcoding, source and sink formats must match. */
> + struct v4l2_mbus_framefmt *fmt;
> +
> + fmt = v4l2_state_get_stream_format(state, pad, stream);
> + if (!fmt)
> + goto out;
> +
> + if (fse->code != fmt->code)
> + goto out;
> +
> + fse->min_width = fmt->width;
> + fse->max_width = fmt->width;
> + fse->min_height = fmt->height;
> + fse->max_height = fmt->height;
> + } else {
> + fmtinfo = unicam_find_format_by_code(fse->code, pad);
> + if (!fmtinfo)
> + goto out;
> +
> + fse->min_width = UNICAM_MIN_WIDTH;
> + fse->max_width = UNICAM_MAX_WIDTH;
> + fse->min_height = UNICAM_MIN_HEIGHT;
> + fse->max_height = UNICAM_MAX_HEIGHT;

You need to enumerate sizes for the image and metadata streams
separately.

> + }
> +
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static const struct v4l2_subdev_video_ops unicam_subdev_video_ops = {
> + .enable_streams = unicam_sd_enable_streams,
> + .disable_streams = unicam_sd_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_pad_ops unicam_subdev_pad_ops = {
> + .init_cfg = unicam_subdev_init_cfg,
> + .enum_mbus_code = unicam_subdev_enum_mbus_code,
> + .get_fmt = v4l2_subdev_get_fmt,
> + .set_fmt = unicam_subdev_set_pad_format,
> + .set_routing = unicam_subdev_set_routing,
> + .enum_frame_size = unicam_subdev_enum_frame_size,
> +};
> +
> +static const struct v4l2_subdev_ops unicam_subdev_ops = {
> + .video = &unicam_subdev_video_ops,
> + .pad = &unicam_subdev_pad_ops,
> +};
> +
> +static const struct media_entity_operations unicam_subdev_media_ops = {
> + .link_validate = v4l2_subdev_link_validate,
> + .has_route = v4l2_subdev_has_route,
> +};
> +
> +static int unicam_init_and_register_subdev(struct unicam_device *unicam)
> +{
> + struct v4l2_subdev *sd = &unicam->subdev.sd;
> + int ret;
> +
> + v4l2_subdev_init(sd, &unicam_subdev_ops);
> + v4l2_set_subdevdata(sd, unicam);
> + unicam->subdev.sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;

sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;

Same thing in many locations below.

> + unicam->subdev.sd.entity.ops = &unicam_subdev_media_ops;
> + unicam->subdev.sd.dev = unicam->dev;
> + unicam->subdev.sd.owner = THIS_MODULE;
> + unicam->subdev.sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_MULTIPLEXED;
> + snprintf(unicam->subdev.sd.name, sizeof(unicam->subdev.sd.name), "unicam-subdev");
> +
> + unicam->subdev.pads[UNICAM_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +
> + unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_IMAGE].flags = MEDIA_PAD_FL_SOURCE;
> + unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_METADATA].flags = MEDIA_PAD_FL_SOURCE;
> +
> + ret = media_entity_pads_init(&sd->entity,
> + ARRAY_SIZE(unicam->subdev.pads), unicam->subdev.pads);

Line wrap.

> + if (ret) {
> + dev_err(unicam->dev, "Could not init media entity");
> + return ret;
> + }
> +
> + ret = v4l2_subdev_init_finalize(sd);
> + if (ret) {
> + dev_err(unicam->dev, "Could not finalize init for subdev");
> + return ret;
> + }
> +
> + return v4l2_device_register_subdev(&unicam->v4l2_dev, sd);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Videobuf2 Queue Operations
> + */
> +
> +static int unicam_queue_setup(struct vb2_queue *vq,
> + unsigned int *nbuffers,
> + unsigned int *nplanes,
> + unsigned int sizes[],
> + struct device *alloc_devs[])
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vq);
> + struct unicam_device *unicam = node->dev;
> + unsigned int size = is_image_node(node) ?
> + node->v_fmt.fmt.pix.sizeimage :
> + node->v_fmt.fmt.meta.buffersize;
> +
> + if (vq->num_buffers + *nbuffers < 3)
> + *nbuffers = 3 - vq->num_buffers;
> +
> + if (*nplanes) {
> + if (sizes[0] < size) {
> + dev_dbg(unicam->dev, "sizes[0] %i < size %u\n", sizes[0],
> + size);
> + return -EINVAL;
> + }
> + size = sizes[0];
> + }
> +
> + *nplanes = 1;
> + sizes[0] = size;
> +
> + return 0;
> +}
> +
> +static int unicam_buffer_prepare(struct vb2_buffer *vb)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
> + struct unicam_device *unicam = node->dev;
> + struct unicam_buffer *buf = to_unicam_buffer(vb);
> + unsigned long size;
> +
> + if (WARN_ON(!node->fmt))
> + return -EINVAL;
> +
> + size = is_image_node(node) ? node->v_fmt.fmt.pix.sizeimage :
> + node->v_fmt.fmt.meta.buffersize;
> + if (vb2_plane_size(vb, 0) < size) {
> + dev_dbg(unicam->dev, "data will not fit into plane (%lu < %lu)\n",
> + vb2_plane_size(vb, 0), size);
> + return -EINVAL;
> + }
> +
> + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
> + return 0;
> +}
> +
> +static void unicam_buffer_queue(struct vb2_buffer *vb)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
> + struct unicam_buffer *buf = to_unicam_buffer(vb);
> +
> + spin_lock_irq(&node->dma_queue_lock);
> + list_add_tail(&buf->list, &node->dma_queue);
> + spin_unlock_irq(&node->dma_queue_lock);
> +}
> +
> +static void unicam_return_buffers(struct unicam_node *node,
> + enum vb2_buffer_state state)
> +{
> + struct unicam_buffer *buf, *tmp;
> +
> + list_for_each_entry_safe(buf, tmp, &node->dma_queue, list) {
> + list_del(&buf->list);
> + vb2_buffer_done(&buf->vb.vb2_buf, state);
> + }
> +
> + if (node->cur_frm)
> + vb2_buffer_done(&node->cur_frm->vb.vb2_buf,
> + state);
> + if (node->next_frm && node->cur_frm != node->next_frm)
> + vb2_buffer_done(&node->next_frm->vb.vb2_buf,
> + state);
> +
> + node->cur_frm = NULL;
> + node->next_frm = NULL;
> +}
> +
> +static int unicam_video_check_format(struct unicam_node *node)
> +{
> + const struct v4l2_mbus_framefmt *format;
> + struct unicam_device *unicam = node->dev;
> + struct v4l2_subdev_state *state;
> + int ret = 0;
> +
> + state = v4l2_subdev_lock_active_state(&unicam->subdev.sd);
> +
> + format = v4l2_state_get_stream_format(state, UNICAM_SD_PAD_SOURCE_IMAGE, 0);
> + if (!format) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + if (node->fmt->code != format->code ||
> + node->v_fmt.fmt.pix.height != format->height ||
> + node->v_fmt.fmt.pix.width != format->width ||
> + node->v_fmt.fmt.pix.field != format->field) {
> + dev_dbg(unicam->dev, "(%u x %u) %08x %s != (%u x %u) %08x %s\n",
> + node->v_fmt.fmt.pix.width, node->v_fmt.fmt.pix.height,
> + node->fmt->code,
> + v4l2_field_names[node->v_fmt.fmt.pix.field],
> + format->width, format->height, format->code,
> + v4l2_field_names[format->field]);
> + ret = -EPIPE;
> + goto out;
> + }
> +
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_start_streaming(struct vb2_queue *vq, unsigned int count)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vq);
> + struct unicam_device *unicam = node->dev;
> + dma_addr_t buffer_addr[UNICAM_MAX_NODES] = { 0 };
> + struct unicam_buffer *buf;
> + struct v4l2_subdev_state *state;
> + unsigned long flags;
> + unsigned int i;
> + int ret;
> + u32 pad, stream;
> + u32 remote_pad_index = is_image_node(node) ? UNICAM_SD_PAD_SOURCE_IMAGE
> + : UNICAM_SD_PAD_SOURCE_METADATA;
> +
> + /* Look for the route for the given pad and stream. */
> + state = v4l2_subdev_lock_active_state(&unicam->subdev.sd);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
> + remote_pad_index, 0,
> + &pad, &stream);

You use neither pad nor stream below. If this check is meant to verify
that a stream corresponding to the video node exists, you could call
v4l2_subdev_state_get_stream_format() instead. The format could then be
passed to unicam_video_check_format() instead of calling the same
function there.

> + if (ret)
> + goto err_streaming;
> +
> + v4l2_subdev_unlock_state(state);
> +
> + dev_dbg(unicam->dev, "Starting stream 0 on pad %d on subdev %s\n",
> + remote_pad_index, unicam->subdev.sd.name);
> +
> + /* The metadata node can't be started alone. */
> + if (is_metadata_node(node)) {
> + if (!unicam->node[UNICAM_IMAGE_NODE].streaming) {
> + dev_err(unicam->dev,
> + "Can't start metadata without image\n");
> + return -EINVAL;
> + }
> + dev_dbg(unicam->dev, "starting metadata node\n");
> +
> + spin_lock_irqsave(&node->dma_queue_lock, flags);
> + buf = list_first_entry(&node->dma_queue,
> + struct unicam_buffer, list);
> + dev_dbg(unicam->dev, "buffer %d: %p\n", i, buf);
> + node->cur_frm = buf;
> + node->next_frm = buf;
> + list_del(&buf->list);
> + spin_unlock_irqrestore(&node->dma_queue_lock, flags);
> +
> + buffer_addr[UNICAM_METADATA_NODE] =
> + vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> + dev_dbg(unicam->dev, "buffer %d addr: %lld\n", i, buffer_addr[i]);
> +
> + unicam_start_metadata(unicam, buffer_addr);
> + node->streaming = true;
> + return 0;
> + }
> +
> + ret = pm_runtime_get_sync(unicam->dev);
> + if (ret < 0) {
> + dev_err(unicam->dev, "pm_runtime_get_sync failed\n");
> + goto err_streaming;
> + }
> +
> + ret = media_pipeline_start(node->video_dev.entity.pads, &unicam->pipe);
> + if (ret < 0) {
> + dev_dbg(unicam->dev, "Failed to start media pipeline: %d\n", ret);
> + goto err_pm_put;
> + }
> +
> + ret = unicam_video_check_format(node);
> + if (ret < 0) {
> + dev_err(unicam->dev, "Video format is incorrect: %d\n", ret);
> + goto error_pipeline;
> + }
> +
> + dev_dbg(unicam->dev, "node %s\n", node->video_dev.name);
> +
> + spin_lock_irqsave(&node->dma_queue_lock, flags);
> + buf = list_first_entry(&node->dma_queue,
> + struct unicam_buffer, list);
> + dev_dbg(unicam->dev, "buffer %d: %p\n", i, buf);
> + node->cur_frm = buf;
> + node->next_frm = buf;
> + list_del(&buf->list);
> + spin_unlock_irqrestore(&node->dma_queue_lock, flags);
> +
> + buffer_addr[UNICAM_IMAGE_NODE] =
> + vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> +
> + dev_dbg(unicam->dev, "Start unicam\n");
> + unicam_start_rx(unicam, buffer_addr);
> +
> + dev_dbg(unicam->dev, "Enable stream\n");
> + ret = v4l2_subdev_call(&unicam->subdev.sd, video, enable_streams,
> + remote_pad_index, BIT(0));
> + if (ret < 0) {
> + dev_err(unicam->dev, "stream on failed in subdev\n");
> + goto error_pipeline;
> + }
> +
> + node->streaming = true;
> +
> + return 0;
> +
> +error_pipeline:
> + media_pipeline_stop(node->video_dev.entity.pads);
> +err_pm_put:
> + pm_runtime_put_sync(unicam->dev);
> +err_streaming:
> + v4l2_subdev_unlock_state(state);
> + return ret;
> +}
> +
> +static void unicam_stop_streaming(struct vb2_queue *vq)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vq);
> + struct unicam_device *unicam = node->dev;
> + u32 remote_pad_index = is_image_node(node) ? UNICAM_SD_PAD_SOURCE_IMAGE
> + : UNICAM_SD_PAD_SOURCE_METADATA;
> +
> + node->streaming = false;
> +
> + v4l2_subdev_call(&unicam->subdev.sd, video, disable_streams,
> + remote_pad_index, BIT(0));
> +
> + /* We can stream only with the image node. */
> + if (is_metadata_node(node)) {
> + /*
> + * Allow the hardware to spin in the dummy buffer.
> + * This is only really needed if the embedded data pad is
> + * disabled before the image pad.
> + */
> + unicam_wr_dma_addr(node, node->dummy_buf_dma_addr,
> + UNICAM_DUMMY_BUF_SIZE);
> + goto dequeue_buffers;
> + }
> +
> + unicam_disable(unicam);
> +
> + media_pipeline_stop(node->video_dev.entity.pads);
> + pm_runtime_put(unicam->dev);
> +
> +dequeue_buffers:
> + /* Clear all queued buffers for the node */
> + unicam_return_buffers(node, VB2_BUF_STATE_ERROR);
> +}
> +
> +static const struct vb2_ops unicam_video_qops = {
> + .wait_prepare = vb2_ops_wait_prepare,
> + .wait_finish = vb2_ops_wait_finish,
> + .queue_setup = unicam_queue_setup,
> + .buf_prepare = unicam_buffer_prepare,
> + .buf_queue = unicam_buffer_queue,
> + .start_streaming = unicam_start_streaming,
> + .stop_streaming = unicam_stop_streaming,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 video device Operations
> + */
> +
> +static int unicam_querycap(struct file *file, void *priv,
> + struct v4l2_capability *cap)
> +{
> + struct unicam_node *node = video_drvdata(file);
> + struct unicam_device *unicam = node->dev;
> +
> + strscpy(cap->driver, UNICAM_MODULE_NAME, sizeof(cap->driver));
> + strscpy(cap->card, UNICAM_MODULE_NAME, sizeof(cap->card));
> +
> + snprintf(cap->bus_info, sizeof(cap->bus_info),
> + "platform:%s", dev_name(unicam->dev));
> +
> + cap->capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE;
> +
> + return 0;
> +}
> +
> +static int unicam_log_status(struct file *file, void *fh)
> +{
> + struct unicam_node *node = video_drvdata(file);
> + struct unicam_device *unicam = node->dev;
> + u32 reg;
> +
> + /* status for sub devices */
> + v4l2_device_call_all(&unicam->v4l2_dev, 0, core, log_status);
> +
> + dev_info(unicam->dev, "-----Receiver status-----\n");
> + dev_info(unicam->dev, "V4L2 width/height: %ux%u\n",
> + node->v_fmt.fmt.pix.width, node->v_fmt.fmt.pix.height);
> + dev_info(unicam->dev, "Mediabus format: %08x\n", node->fmt->code);
> + dev_info(unicam->dev, "V4L2 format: %08x\n",
> + node->v_fmt.fmt.pix.pixelformat);
> + reg = unicam_reg_read(unicam, UNICAM_IPIPE);
> + dev_info(unicam->dev, "Unpacking/packing: %u / %u\n",
> + unicam_get_field(reg, UNICAM_PUM_MASK),
> + unicam_get_field(reg, UNICAM_PPM_MASK));
> + dev_info(unicam->dev, "----Live data----\n");
> + dev_info(unicam->dev, "Programmed stride: %4u\n",
> + unicam_reg_read(unicam, UNICAM_IBLS));
> + dev_info(unicam->dev, "Detected resolution: %ux%u\n",
> + unicam_reg_read(unicam, UNICAM_IHSTA),
> + unicam_reg_read(unicam, UNICAM_IVSTA));
> + dev_info(unicam->dev, "Write pointer: %08x\n",
> + unicam_reg_read(unicam, UNICAM_IBWP));
> +
> + return 0;
> +}
> +
> +static int unicam_subscribe_event(struct v4l2_fh *fh,
> + const struct v4l2_event_subscription *sub)
> +{
> + switch (sub->type) {
> + case V4L2_EVENT_FRAME_SYNC:
> + return v4l2_event_subscribe(fh, sub, 2, NULL);
> + }
> +
> + return v4l2_ctrl_subscribe_event(fh, sub);
> +}
> +
> +static int unicam_enum_fmt_vid(struct file *file, void *priv,
> + struct v4l2_fmtdesc *f)
> +{
> + unsigned int i, index;
> +
> + for (i = 0, index = 0; i < ARRAY_SIZE(unicam_image_formats); i++) {
> + if (f->mbus_code && unicam_image_formats[i].code != f->mbus_code)
> + continue;
> +
> + if (index == f->index) {
> + f->pixelformat = unicam_image_formats[i].fourcc;
> + return 0;
> + }
> +
> + index++;
> +
> + if (unicam_image_formats[i].unpacked_fourcc) {
> + if (index == f->index) {
> + f->pixelformat = unicam_image_formats[i].unpacked_fourcc;
> + return 0;
> + }
> + index++;
> + }
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int unicam_g_fmt_vid(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> +
> + *f = node->v_fmt;
> +
> + return 0;
> +}
> +
> +static const struct unicam_fmt *
> +unicam_try_fmt(struct unicam_node *node, struct v4l2_format *f)
> +{
> + struct v4l2_pix_format *v4l2_format = &f->fmt.pix;
> + struct unicam_device *unicam = node->dev;
> + const struct unicam_fmt *fmt;
> +
> + /*
> + * Default to the first format if the requested pixel format code isn't
> + * supported.
> + */
> + fmt = unicam_find_format_by_fourcc(v4l2_format->pixelformat,
> + UNICAM_SD_PAD_SOURCE_IMAGE);
> + if (!fmt) {
> + fmt = &unicam_image_formats[0];
> + v4l2_format->pixelformat = fmt->fourcc;
> + }
> +
> + unicam_calc_format_size_bpl(unicam, fmt, f);
> +
> + if (v4l2_format->field == V4L2_FIELD_ANY)
> + v4l2_format->field = V4L2_FIELD_NONE;
> +
> + dev_dbg(unicam->dev, "%s: %08x %ux%u (bytesperline %u sizeimage %u)\n",

You can use %p4cc here too (don't forget that you then need to pass the
address of the pixelformat variable, not its value).

> + __func__, v4l2_format->pixelformat,
> + v4l2_format->width, v4l2_format->height,
> + v4l2_format->bytesperline, v4l2_format->sizeimage);
> +
> + return fmt;
> +}
> +
> +static int unicam_try_fmt_vid(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> +
> + unicam_try_fmt(node, f);
> + return 0;
> +}
> +
> +static int unicam_s_fmt_vid(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> + struct unicam_device *unicam = node->dev;
> +
> + if (vb2_is_busy(&node->buffer_queue)) {
> + dev_dbg(unicam->dev, "%s device busy\n", __func__);
> + return -EBUSY;
> + }
> +
> + node->fmt = unicam_try_fmt(node, f);
> +
> + node->v_fmt = *f;
> +
> + return 0;
> +}
> +
> +static int unicam_enum_framesizes(struct file *file, void *fh,
> + struct v4l2_frmsizeenum *fsize)
> +{
> + struct unicam_node *node = video_drvdata(file);
> + int ret = -EINVAL;

Drop ret, and return -EINVAL explicitly below.

> +
> + if (fsize->index > 0)
> + return ret;
> +
> + if (is_image_node(node)) {
> + if (!unicam_find_format_by_fourcc(fsize->pixel_format,
> + UNICAM_SD_PAD_SOURCE_IMAGE))
> + return ret;
> +
> + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> + fsize->stepwise.min_width = UNICAM_MIN_WIDTH;
> + fsize->stepwise.max_width = UNICAM_MAX_WIDTH;
> + fsize->stepwise.step_width = 1;
> + fsize->stepwise.min_height = UNICAM_MIN_HEIGHT;
> + fsize->stepwise.max_height = UNICAM_MAX_HEIGHT;
> + fsize->stepwise.step_height = 1;
> + } else {
> + if (!unicam_find_format_by_fourcc(fsize->pixel_format,
> + UNICAM_SD_PAD_SOURCE_METADATA))
> + return ret;
> +
> + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
> + fsize->discrete.width = UNICAM_EMBEDDED_SIZE;
> + fsize->discrete.height = 1;
> + }
> +
> + return 0;
> +}
> +
> +static int unicam_enum_fmt_meta(struct file *file, void *priv,
> + struct v4l2_fmtdesc *f)
> +{
> + unsigned int i, index;
> +
> + for (i = 0, index = 0; i < ARRAY_SIZE(unicam_meta_formats); i++) {
> + if (f->mbus_code && unicam_meta_formats[i].code != f->mbus_code)
> + continue;
> + if (!unicam_meta_formats[i].metadata_fmt)
> + continue;
> +
> + if (index == f->index) {
> + f->pixelformat = unicam_meta_formats[i].fourcc;
> + f->type = V4L2_BUF_TYPE_META_CAPTURE;
> + return 0;
> + }
> + index++;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int unicam_g_fmt_meta(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> +
> + f->fmt.meta = node->v_fmt.fmt.meta;
> +
> + return 0;
> +}
> +
> +static int unicam_try_fmt_meta(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + f->fmt.meta.dataformat = V4L2_META_FMT_8;
> + f->fmt.meta.buffersize = UNICAM_EMBEDDED_SIZE;
> +
> + return 0;
> +}
> +
> +static int unicam_s_fmt_meta(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> +
> + unicam_try_fmt_meta(file, priv, f);
> +
> + node->v_fmt = *f;
> +
> + return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops unicam_ioctl_ops = {
> + .vidioc_querycap = unicam_querycap,
> + .vidioc_enum_fmt_vid_cap = unicam_enum_fmt_vid,
> + .vidioc_g_fmt_vid_cap = unicam_g_fmt_vid,
> + .vidioc_try_fmt_vid_cap = unicam_try_fmt_vid,
> + .vidioc_s_fmt_vid_cap = unicam_s_fmt_vid,
> +
> + .vidioc_enum_fmt_meta_cap = unicam_enum_fmt_meta,
> + .vidioc_g_fmt_meta_cap = unicam_g_fmt_meta,
> + .vidioc_try_fmt_meta_cap = unicam_try_fmt_meta,
> + .vidioc_s_fmt_meta_cap = unicam_s_fmt_meta,
> +
> + .vidioc_enum_framesizes = unicam_enum_framesizes,
> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
> + .vidioc_create_bufs = vb2_ioctl_create_bufs,
> + .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> + .vidioc_querybuf = vb2_ioctl_querybuf,
> + .vidioc_qbuf = vb2_ioctl_qbuf,
> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
> + .vidioc_expbuf = vb2_ioctl_expbuf,
> + .vidioc_streamon = vb2_ioctl_streamon,
> + .vidioc_streamoff = vb2_ioctl_streamoff,
> +
> + .vidioc_log_status = unicam_log_status,
> + .vidioc_subscribe_event = unicam_subscribe_event,
> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +/* unicam capture driver file operations */
> +static const struct v4l2_file_operations unicam_fops = {
> + .owner = THIS_MODULE,
> + .open = v4l2_fh_open,
> + .release = vb2_fop_release,
> + .poll = vb2_fop_poll,
> + .unlocked_ioctl = video_ioctl2,
> + .mmap = vb2_fop_mmap,
> +};
> +
> +static void unicam_set_default_format(struct unicam_node *node)
> +{
> + if (is_image_node(node)) {
> + struct v4l2_pix_format *pix_fmt = &node->v_fmt.fmt.pix;
> +
> + pix_fmt->width = default_image_format.width;
> + pix_fmt->height = default_image_format.height;
> + pix_fmt->field = default_image_format.field;
> + pix_fmt->colorspace = default_image_format.colorspace;
> + pix_fmt->ycbcr_enc = default_image_format.ycbcr_enc;
> + pix_fmt->quantization = default_image_format.quantization;
> + pix_fmt->xfer_func = default_image_format.xfer_func;
> + pix_fmt->pixelformat = unicam_image_formats[0].fourcc;
> + unicam_calc_format_size_bpl(node->dev, &unicam_image_formats[0],
> + &node->v_fmt);
> + node->v_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +
> + node->fmt = &unicam_image_formats[0];
> + } else {
> + const struct unicam_fmt *fmt;
> +
> + /* Fix this node format as embedded data. */
> + fmt = &unicam_meta_formats[0];
> + node->fmt = fmt;
> + node->v_fmt.fmt.meta.dataformat = fmt->fourcc;
> + node->v_fmt.fmt.meta.buffersize = UNICAM_EMBEDDED_SIZE;
> + node->v_fmt.type = V4L2_BUF_TYPE_META_CAPTURE;
> + }
> +}
> +
> +static int unicam_register_node(struct unicam_device *unicam,
> + enum unicam_node_type type)
> +{
> + struct video_device *vdev;
> + struct vb2_queue *q;
> + struct unicam_node *node = &unicam->node[type];
> + int ret;
> + u32 pad_index = type == UNICAM_IMAGE_NODE ? UNICAM_SD_PAD_SOURCE_IMAGE
> + : UNICAM_SD_PAD_SOURCE_METADATA;
> +
> + node->dev = unicam_get(unicam);
> + node->id = type;
> +
> + spin_lock_init(&node->dma_queue_lock);
> + mutex_init(&node->lock);
> +
> + INIT_LIST_HEAD(&node->dma_queue);
> +
> + /* Initialize the videobuf2 queue. */
> + q = &node->buffer_queue;
> + q->type = type == UNICAM_IMAGE_NODE ? V4L2_BUF_TYPE_VIDEO_CAPTURE
> + : V4L2_BUF_TYPE_META_CAPTURE;
> + q->io_modes = VB2_MMAP | VB2_DMABUF;
> + q->drv_priv = node;
> + q->ops = &unicam_video_qops;
> + q->mem_ops = &vb2_dma_contig_memops;
> + q->buf_struct_size = sizeof(struct unicam_buffer);
> + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> + q->lock = &node->lock;
> + q->min_buffers_needed = 1;
> + q->dev = unicam->dev;
> +
> + ret = vb2_queue_init(q);
> + if (ret) {
> + dev_err(unicam->dev, "vb2_queue_init() failed\n");
> + goto err_unicam_put;
> + }
> +
> + /* Initialize the video device. */
> + vdev = &node->video_dev;
> + vdev->release = unicam_node_release;
> + vdev->fops = &unicam_fops;
> + vdev->ioctl_ops = &unicam_ioctl_ops;
> + vdev->v4l2_dev = &unicam->v4l2_dev;
> + vdev->vfl_dir = VFL_DIR_RX;
> + vdev->queue = q;
> + vdev->lock = &node->lock;
> + vdev->device_caps = type == UNICAM_IMAGE_NODE ?
> + V4L2_CAP_VIDEO_CAPTURE : V4L2_CAP_META_CAPTURE;
> + vdev->device_caps |= V4L2_CAP_STREAMING | V4L2_CAP_IO_MC;
> +
> + /* Define the device names */
> + snprintf(vdev->name, sizeof(vdev->name), "%s-%s", UNICAM_MODULE_NAME,
> + type == UNICAM_IMAGE_NODE ? "image" : "embedded");
> +
> + video_set_drvdata(vdev, node);
> + if (type == UNICAM_IMAGE_NODE)
> + vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT;
> + node->pad.flags = MEDIA_PAD_FL_SINK;
> + ret = media_entity_pads_init(&vdev->entity, 1, &node->pad);
> + if (ret)
> + goto err_unicam_put;
> +
> + node->dummy_buf_cpu_addr = dma_alloc_coherent(unicam->dev,
> + UNICAM_DUMMY_BUF_SIZE,
> + &node->dummy_buf_dma_addr,
> + GFP_KERNEL);
> + if (!node->dummy_buf_cpu_addr) {
> + dev_err(unicam->dev, "Unable to allocate dummy buffer.\n");
> + ret = -ENOMEM;
> + goto err_entity_cleanup;
> + }
> +
> + unicam_set_default_format(node);
> +
> + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> + if (ret) {
> + dev_err(unicam->dev, "Unable to register video device %s\n",
> + vdev->name);
> + goto err_dma_free;
> + }
> +
> + node->registered = true;
> +
> + ret = media_create_pad_link(&unicam->subdev.sd.entity,
> + pad_index,
> + &node->video_dev.entity,
> + 0,
> + MEDIA_LNK_FL_ENABLED |
> + MEDIA_LNK_FL_IMMUTABLE);
> + if (ret) {
> + /*
> + * No need for cleanup, the caller will unregister the
> + * video device, which will drop the reference on the
> + * device and trigger the cleanup.
> + */
> + dev_err(unicam->dev, "Unable to create pad link for %s",
> + unicam->sensor.subdev->name);
> + return ret;
> + }
> +
> + return 0;
> +
> +err_dma_free:
> + dma_free_coherent(unicam->dev, UNICAM_DUMMY_BUF_SIZE,
> + node->dummy_buf_cpu_addr,
> + node->dummy_buf_dma_addr);
> +err_entity_cleanup:
> + media_entity_cleanup(&vdev->entity);
> +err_unicam_put:
> + unicam_put(unicam);
> + return ret;
> +}
> +
> +static void unicam_unregister_nodes(struct unicam_device *unicam)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + struct unicam_node *node = &unicam->node[i];
> +
> + if (node->dummy_buf_cpu_addr)
> + dma_free_coherent(unicam->dev, UNICAM_DUMMY_BUF_SIZE,
> + node->dummy_buf_cpu_addr,
> + node->dummy_buf_dma_addr);
> +
> + if (node->registered) {
> + video_unregister_device(&node->video_dev);
> + node->registered = false;
> + }
> + }
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 async notifier
> + */
> +
> +static int
> +unicam_async_bound(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *subdev,
> + struct v4l2_async_subdev *asd)
> +{
> + struct unicam_device *unicam = notifier_to_unicam_device(notifier);
> + struct media_pad *sink = &unicam->subdev.sd.entity.pads[UNICAM_SD_PAD_SINK];
> +
> + unicam->sensor.subdev = subdev;
> + dev_dbg(unicam->dev, "Using sensor %s for capture\n",
> + subdev->name);

This holds on a single line.

> +
> + unicam->sensor.pad = sink;

Is this the right pad, shouldn't sensor.pad be the source pad of the
*sensor* ?

> +
> + return v4l2_create_fwnode_links_to_pad(subdev, sink, MEDIA_LNK_FL_ENABLED |
> + MEDIA_LNK_FL_IMMUTABLE);
> +}
> +
> +static int unicam_async_complete(struct v4l2_async_notifier *notifier)
> +{
> + struct unicam_device *unicam = notifier_to_unicam_device(notifier);
> + int ret;
> +
> + ret = unicam_register_node(unicam, UNICAM_IMAGE_NODE);
> + if (ret) {
> + dev_err(unicam->dev, "Unable to register image video device.\n");
> + goto unregister;
> + }
> +
> + ret = unicam_register_node(unicam, UNICAM_METADATA_NODE);
> + if (ret) {
> + dev_err(unicam->dev, "Unable to register metadata video device.\n");
> + goto unregister;
> + }
> +
> + ret = v4l2_device_register_subdev_nodes(&unicam->v4l2_dev);
> + if (ret) {
> + dev_err(unicam->dev, "Unable to register subdev nodes.\n");
> + goto unregister;
> + }
> +
> + return 0;
> +
> +unregister:
> + unicam_unregister_nodes(unicam);
> + unicam_put(unicam);
> +
> + return ret;
> +}
> +
> +static const struct v4l2_async_notifier_operations unicam_async_ops = {
> + .bound = unicam_async_bound,
> + .complete = unicam_async_complete,
> +};
> +
> +static int unicam_connect_of_subdevs(struct unicam_device *unicam)
> +{
> + struct v4l2_fwnode_endpoint ep = { };
> + struct fwnode_handle *ep_handle;
> + struct v4l2_async_subdev *asd;
> + unsigned int lane;
> + int ret = -EINVAL;
> +
> + if (of_property_read_u32(unicam->dev->of_node, "brcm,num-data-lanes",
> + &unicam->max_data_lanes) < 0) {
> + dev_err(unicam->dev, "DT property %s not set\n",
> + "brcm,num-data-lanes");
> + return -EINVAL;
> + }
> +
> + /* Get the local endpoint and remote device. */
> + ep_handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(unicam->dev),
> + 0, 0,
> + FWNODE_GRAPH_ENDPOINT_NEXT);
> + if (!ep_handle) {
> + dev_err(unicam->dev, "No endpoint\n");
> + return -ENODEV;
> + }
> +
> + /* Parse the local endpoint and validate its configuration. */
> + if (v4l2_fwnode_endpoint_alloc_parse(ep_handle, &ep)) {
> + dev_err(unicam->dev, "could not parse endpoint\n");
> + goto cleanup_exit;
> + }
> +
> + dev_dbg(unicam->dev, "parsed local endpoint, bus_type %u\n",
> + ep.bus_type);
> +
> + unicam->bus_type = ep.bus_type;
> +
> + switch (ep.bus_type) {
> + case V4L2_MBUS_CSI2_DPHY:
> + switch (ep.bus.mipi_csi2.num_data_lanes) {
> + case 1:
> + case 2:
> + case 4:
> + break;
> +
> + default:
> + dev_err(unicam->dev, "%u data lanes not supported\n",
> + ep.bus.mipi_csi2.num_data_lanes);
> + goto cleanup_exit;
> + }
> +
> + for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
> + if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
> + dev_err(unicam->dev, "data lanes reordering not supported\n");
> + goto cleanup_exit;
> + }
> + }
> +
> + if (ep.bus.mipi_csi2.num_data_lanes > unicam->max_data_lanes) {
> + dev_err(unicam->dev, "endpoint requires %u data lanes when %u are supported\n",
> + ep.bus.mipi_csi2.num_data_lanes,
> + unicam->max_data_lanes);
> + }
> +
> + unicam->active_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
> + unicam->bus_flags = ep.bus.mipi_csi2.flags;
> +
> + break;
> +
> + case V4L2_MBUS_CCP2:
> + if (ep.bus.mipi_csi1.clock_lane != 0 ||
> + ep.bus.mipi_csi1.data_lane != 1) {
> + dev_err(unicam->dev, "unsupported lanes configuration\n");
> + goto cleanup_exit;
> + }
> +
> + unicam->max_data_lanes = 1;
> + unicam->active_data_lanes = 1;
> + unicam->bus_flags = ep.bus.mipi_csi1.strobe;
> + break;
> +
> + default:
> + /* Unsupported bus type */
> + dev_err(unicam->dev, "unsupported bus type %u\n",
> + ep.bus_type);

This holds on a single line.

> + goto cleanup_exit;
> + }
> +
> + dev_dbg(unicam->dev, "%s bus, %u data lanes, flags=0x%08x\n",
> + unicam->bus_type == V4L2_MBUS_CSI2_DPHY ? "CSI-2" : "CCP2",
> + unicam->active_data_lanes, unicam->bus_flags);
> +
> + /* Initialize and register the async notifier. */
> + v4l2_async_nf_init(&unicam->notifier);
> +
> + asd = v4l2_async_nf_add_fwnode_remote(&unicam->notifier, ep_handle,
> + struct v4l2_async_subdev);
> +
> + fwnode_handle_put(ep_handle);
> + ep_handle = NULL;
> +
> + if (IS_ERR(asd)) {
> + ret = PTR_ERR(asd);
> + goto cleanup_exit;
> + }
> +
> + unicam->notifier.ops = &unicam_async_ops;
> +
> + ret = v4l2_async_nf_register(&unicam->v4l2_dev, &unicam->notifier);
> + if (ret) {
> + dev_err(unicam->dev, "Error registering device notifier: %d\n", ret);
> + goto cleanup_exit;
> + }
> +
> + return 0;
> +
> +cleanup_exit:
> + v4l2_fwnode_endpoint_free(&ep);
> + fwnode_handle_put(ep_handle);
> +
> + return ret;
> +}
> +
> +static int unicam_media_dev_init(struct unicam_device *unicam)
> +{
> + int ret = 0;

No need to initialize ret to 0.

> +
> + unicam->mdev.dev = unicam->dev;
> + strscpy(unicam->mdev.model, UNICAM_MODULE_NAME,
> + sizeof(unicam->mdev.model));
> + strscpy(unicam->mdev.serial, "", sizeof(unicam->mdev.serial));
> + snprintf(unicam->mdev.bus_info, sizeof(unicam->mdev.bus_info),
> + "platform:%s", dev_name(unicam->dev));
> + unicam->mdev.hw_revision = 0;
> +
> + media_device_init(&unicam->mdev);
> +
> + unicam->v4l2_dev.mdev = &unicam->mdev;
> +
> + ret = v4l2_device_register(unicam->dev, &unicam->v4l2_dev);
> + if (ret < 0) {
> + dev_err(unicam->dev,
> + "Unable to register v4l2 device\n");

This holds on a single line.

> + return ret;
> + }
> +
> + ret = media_device_register(&unicam->mdev);
> + if (ret < 0) {
> + dev_err(unicam->dev,
> + "Unable to register media-controller device\n");

s/media-controller/media/

and it will then hold on a single line.

> + goto err_v4l2_unregister;
> + }
> +
> + return 0;
> +
> +err_v4l2_unregister:
> + v4l2_device_unregister(&unicam->v4l2_dev);
> + return ret;
> +}
> +
> +static int unicam_probe(struct platform_device *pdev)
> +{
> + struct unicam_device *unicam;
> + int ret = 0;
> +
> + unicam = kzalloc(sizeof(*unicam), GFP_KERNEL);
> + if (!unicam)
> + return -ENOMEM;
> +
> + kref_init(&unicam->kref);
> + unicam->dev = &pdev->dev;
> + /* set the driver data in platform device */
> + platform_set_drvdata(pdev, unicam);
> +
> + unicam->base = devm_platform_ioremap_resource_byname(pdev, "unicam");
> + if (IS_ERR(unicam->base)) {
> + ret = PTR_ERR(unicam->base);
> + goto err_unicam_put;
> + }
> +
> + unicam->clk_gate_base = devm_platform_ioremap_resource_byname(pdev, "cmi");
> + if (IS_ERR(unicam->clk_gate_base)) {
> + ret = PTR_ERR(unicam->clk_gate_base);
> + goto err_unicam_put;
> + }
> +
> + unicam->clock = devm_clk_get(&pdev->dev, "lp");
> + if (IS_ERR(unicam->clock)) {
> + dev_err(unicam->dev, "Failed to get lp clock\n");

If you wrote

dev_err(unicam->dev, "Failed to get %s clock\n", "lp");

and similarly for the vpu clock, the compiler should be able to
de-duplicate the format string, possibly resulting in a slightly smaller
binary. I'm not entirely sure if the increase in code size due to the
extra parameter would result in a net gain though.

> + ret = PTR_ERR(unicam->clock);
> + goto err_unicam_put;
> + }
> +
> + unicam->vpu_clock = devm_clk_get(&pdev->dev, "vpu");
> + if (IS_ERR(unicam->vpu_clock)) {
> + dev_err(unicam->dev, "Failed to get vpu clock\n");
> + ret = PTR_ERR(unicam->vpu_clock);
> + goto err_unicam_put;
> + }
> +
> + ret = platform_get_irq(pdev, 0);
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "No IRQ resource\n");
> + ret = -EINVAL;
> + goto err_unicam_put;
> + }
> +
> + ret = devm_request_irq(&pdev->dev, ret, unicam_isr, 0,
> + "unicam_capture0", unicam);
> + if (ret) {
> + dev_err(&pdev->dev, "Unable to request interrupt\n");
> + ret = -EINVAL;
> + goto err_unicam_put;
> + }
> +
> + ret = unicam_media_dev_init(unicam);
> + if (ret) {
> + dev_err(unicam->dev,
> + "Unable to initialize media device\n");
> + goto err_unicam_put;
> + }
> +
> + ret = unicam_init_and_register_subdev(unicam);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to register internal subdev\n");
> + goto err_media_unregister;
> + }
> +
> + ret = unicam_connect_of_subdevs(unicam);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to connect subdevs\n");
> + goto err_subdev_unregister;
> + }
> +
> + /* Enable the block power domain */
> + pm_runtime_enable(&pdev->dev);
> +
> + return 0;
> +
> +err_subdev_unregister:
> + v4l2_subdev_cleanup(&unicam->subdev.sd);
> +err_media_unregister:
> + media_device_unregister(&unicam->mdev);
> +err_unicam_put:
> + unicam_put(unicam);
> +
> + return ret;
> +}
> +
> +static int unicam_remove(struct platform_device *pdev)
> +{
> + struct unicam_device *unicam = platform_get_drvdata(pdev);
> +
> + unicam_unregister_nodes(unicam);
> + v4l2_device_unregister(&unicam->v4l2_dev);
> + media_device_unregister(&unicam->mdev);
> + v4l2_async_nf_unregister(&unicam->notifier);
> + unicam_put(unicam);
> +
> + pm_runtime_disable(&pdev->dev);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id unicam_of_match[] = {
> + { .compatible = "brcm,bcm2835-unicam", },
> + { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, unicam_of_match);
> +
> +static struct platform_driver unicam_driver = {
> + .probe = unicam_probe,
> + .remove = unicam_remove,
> + .driver = {
> + .name = UNICAM_MODULE_NAME,
> + .of_match_table = of_match_ptr(unicam_of_match),
> + },
> +};
> +
> +module_platform_driver(unicam_driver);
> +
> +MODULE_AUTHOR("Dave Stevenson <[email protected]>");
> +MODULE_DESCRIPTION("BCM2835 Unicam driver");
> +MODULE_LICENSE("GPL");

--
Regards,

Laurent Pinchart

2022-02-24 01:18:23

by Jean-Michel Hautbois

[permalink] [raw]
Subject: [PATCH v5.1 1/2] ARM: dts: bcm2835-rpi: Move the firmware clocks

The bcm2835-rpi.dtsi is used by all Raspberry Pi generations. Move the
firmware clocks from bcm2711 specific dtsi to the general dtsi.

Signed-off-by: Jean-Michel Hautbois <[email protected]>
---
arch/arm/boot/dts/bcm2711-rpi.dtsi | 5 -----
arch/arm/boot/dts/bcm2835-rpi.dtsi | 5 +++++
2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/arch/arm/boot/dts/bcm2711-rpi.dtsi b/arch/arm/boot/dts/bcm2711-rpi.dtsi
index ca266c5d9f9b..93b96cebe949 100644
--- a/arch/arm/boot/dts/bcm2711-rpi.dtsi
+++ b/arch/arm/boot/dts/bcm2711-rpi.dtsi
@@ -19,11 +19,6 @@ aliases {
};

&firmware {
- firmware_clocks: clocks {
- compatible = "raspberrypi,firmware-clocks";
- #clock-cells = <1>;
- };
-
expgpio: gpio {
compatible = "raspberrypi,firmware-gpio";
gpio-controller;
diff --git a/arch/arm/boot/dts/bcm2835-rpi.dtsi b/arch/arm/boot/dts/bcm2835-rpi.dtsi
index 87ddcad76083..8f988dd253fc 100644
--- a/arch/arm/boot/dts/bcm2835-rpi.dtsi
+++ b/arch/arm/boot/dts/bcm2835-rpi.dtsi
@@ -21,6 +21,11 @@ firmware: firmware {
dma-ranges;
};

+ firmware_clocks: clocks {
+ compatible = "raspberrypi,firmware-clocks";
+ #clock-cells = <1>;
+ };
+
power: power {
compatible = "raspberrypi,bcm2835-power";
firmware = <&firmware>;
--
2.32.0

2022-02-25 12:43:13

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Hi Jean-Michel,

Thanks for the set. This is definitely best Unicam driver I've seen!

I still haven't looked at Tomi's latest set so I may have further comments
after that. I'd be happy to be cc'd in future versions of this set, too.

On Tue, Feb 08, 2022 at 04:50:20PM +0100, Jean-Michel Hautbois wrote:
> Add driver for the Unicam camera receiver block on BCM283x processors.
> It is represented as two video device nodes: unicam-image and
> unicam-embedded which are connected to an internal subdev (named
> unicam-subdev) in order to manage streams routing.
>
> Signed-off-by: Dave Stevenson <[email protected]>
> Signed-off-by: Naushir Patuck <[email protected]>
> Signed-off-by: Jean-Michel Hautbois <[email protected]>
>
> ---
> v4:
> - Add the vendor prefox for DT name
> - Use the reg-names in DT parsing
> - Remove MAINTAINERS entry
>
> v3 main changes:
> - Change code organization
> - Remove unused variables
> - Correct the fmt_meta functions
> - Rewrite the start/stop streaming
> - You can now start the image node alone, but not the metadata one
> - The buffers are allocated per-node
> - only the required stream is started, if the route exists and is
> enabled
> - Prefix the macros with UNICAM_ to not have too generic names
> - Drop colorspace support
> -> This is causing issues in the try-fmt v4l2-compliance test
> test VIDIOC_G_FMT: OK
> fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> test VIDIOC_TRY_FMT: FAIL
> fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> test VIDIOC_S_FMT: FAIL
>
> v2: Remove the unicam_{info,debug,error} macros and use
> dev_dbg/dev_err instead.
> ---
> drivers/media/platform/Kconfig | 1 +
> drivers/media/platform/Makefile | 2 +
> drivers/media/platform/bcm2835/Kconfig | 21 +
> drivers/media/platform/bcm2835/Makefile | 3 +
> .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> 6 files changed, 2850 insertions(+)
> create mode 100644 drivers/media/platform/bcm2835/Kconfig
> create mode 100644 drivers/media/platform/bcm2835/Makefile
> create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c
>
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 9fbdba0fd1e7..033b0358fbb8 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -170,6 +170,7 @@ source "drivers/media/platform/am437x/Kconfig"
> source "drivers/media/platform/xilinx/Kconfig"
> source "drivers/media/platform/rcar-vin/Kconfig"
> source "drivers/media/platform/atmel/Kconfig"
> +source "drivers/media/platform/bcm2835/Kconfig"
> source "drivers/media/platform/sunxi/Kconfig"
>
> config VIDEO_TI_CAL
> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> index 28eb4aadbf45..18894fc586aa 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -86,6 +86,8 @@ obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom/camss/
>
> obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/
>
> +obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835/
> +
> obj-y += sunxi/
>
> obj-$(CONFIG_VIDEO_MESON_GE2D) += meson/ge2d/
> diff --git a/drivers/media/platform/bcm2835/Kconfig b/drivers/media/platform/bcm2835/Kconfig
> new file mode 100644
> index 000000000000..1691541da905
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/Kconfig
> @@ -0,0 +1,21 @@
> +# Broadcom VideoCore4 V4L2 camera support
> +
> +config VIDEO_BCM2835_UNICAM
> + tristate "Broadcom BCM283x/BCM271x Unicam video capture driver"
> + depends on ARCH_BCM2835 || COMPILE_TEST
> + depends on VIDEO_V4L2
> + select MEDIA_CONTROLLER
> + select VIDEO_V4L2_SUBDEV_API
> + select V4L2_FWNODE
> + select VIDEOBUF2_DMA_CONTIG
> + help
> + Say Y here to enable support for the BCM283x/BCM271x CSI-2 receiver.
> + This is a V4L2 driver that controls the CSI-2 receiver directly,
> + independently from the VC4 firmware.
> + This driver is mutually exclusive with the use of bcm2835-camera. The
> + firmware will disable all access to the peripheral from within the
> + firmware if it finds a DT node using it, and bcm2835-camera will
> + therefore fail to probe.
> +
> + To compile this driver as a module, choose M here. The module will be
> + called bcm2835-unicam.
> diff --git a/drivers/media/platform/bcm2835/Makefile b/drivers/media/platform/bcm2835/Makefile
> new file mode 100644
> index 000000000000..a98aba03598a
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/Makefile
> @@ -0,0 +1,3 @@
> +# Makefile for BCM2835 Unicam driver
> +
> +obj-$(CONFIG_VIDEO_BCM2835_UNICAM) += bcm2835-unicam.o
> diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> new file mode 100644
> index 000000000000..b8d297076a02
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> @@ -0,0 +1,253 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +
> +/*
> + * Copyright (C) 2017-2020 Raspberry Pi Trading.
> + * Dave Stevenson <[email protected]>
> + */
> +
> +#ifndef VC4_REGS_UNICAM_H
> +#define VC4_REGS_UNICAM_H
> +
> +/*
> + * The following values are taken from files found within the code drop
> + * made by Broadcom for the BCM21553 Graphics Driver, predominantly in
> + * brcm_usrlib/dag/vmcsx/vcinclude/hardware_vc4.h.
> + * They have been modified to be only the register offset.
> + */
> +#define UNICAM_CTRL 0x000
> +#define UNICAM_STA 0x004
> +#define UNICAM_ANA 0x008
> +#define UNICAM_PRI 0x00c
> +#define UNICAM_CLK 0x010
> +#define UNICAM_CLT 0x014
> +#define UNICAM_DAT0 0x018
> +#define UNICAM_DAT1 0x01c
> +#define UNICAM_DAT2 0x020
> +#define UNICAM_DAT3 0x024
> +#define UNICAM_DLT 0x028
> +#define UNICAM_CMP0 0x02c
> +#define UNICAM_CMP1 0x030
> +#define UNICAM_CAP0 0x034
> +#define UNICAM_CAP1 0x038
> +#define UNICAM_ICTL 0x100
> +#define UNICAM_ISTA 0x104
> +#define UNICAM_IDI0 0x108
> +#define UNICAM_IPIPE 0x10c
> +#define UNICAM_IBSA0 0x110
> +#define UNICAM_IBEA0 0x114
> +#define UNICAM_IBLS 0x118
> +#define UNICAM_IBWP 0x11c
> +#define UNICAM_IHWIN 0x120
> +#define UNICAM_IHSTA 0x124
> +#define UNICAM_IVWIN 0x128
> +#define UNICAM_IVSTA 0x12c
> +#define UNICAM_ICC 0x130
> +#define UNICAM_ICS 0x134
> +#define UNICAM_IDC 0x138
> +#define UNICAM_IDPO 0x13c
> +#define UNICAM_IDCA 0x140
> +#define UNICAM_IDCD 0x144
> +#define UNICAM_IDS 0x148
> +#define UNICAM_DCS 0x200
> +#define UNICAM_DBSA0 0x204
> +#define UNICAM_DBEA0 0x208
> +#define UNICAM_DBWP 0x20c
> +#define UNICAM_DBCTL 0x300
> +#define UNICAM_IBSA1 0x304
> +#define UNICAM_IBEA1 0x308
> +#define UNICAM_IDI1 0x30c
> +#define UNICAM_DBSA1 0x310
> +#define UNICAM_DBEA1 0x314
> +#define UNICAM_MISC 0x400
> +
> +/*
> + * The following bitmasks are from the kernel released by Broadcom
> + * for Android - https://android.googlesource.com/kernel/bcm/
> + * The Rhea, Hawaii, and Java chips all contain the same VideoCore4
> + * Unicam block as BCM2835, as defined in eg
> + * arch/arm/mach-rhea/include/mach/rdb_A0/brcm_rdb_cam.h and similar.
> + * Values reworked to use the kernel BIT and GENMASK macros.
> + *
> + * Some of the bit mnenomics have been amended to match the datasheet.
> + */
> +/* UNICAM_CTRL Register */
> +#define UNICAM_CPE BIT(0)
> +#define UNICAM_MEM BIT(1)
> +#define UNICAM_CPR BIT(2)
> +#define UNICAM_CPM_MASK GENMASK(3, 3)
> +#define UNICAM_CPM_CSI2 0
> +#define UNICAM_CPM_CCP2 1
> +#define UNICAM_SOE BIT(4)
> +#define UNICAM_DCM_MASK GENMASK(5, 5)
> +#define UNICAM_DCM_STROBE 0
> +#define UNICAM_DCM_DATA 1
> +#define UNICAM_SLS BIT(6)
> +#define UNICAM_PFT_MASK GENMASK(11, 8)
> +#define UNICAM_OET_MASK GENMASK(20, 12)
> +
> +/* UNICAM_STA Register */
> +#define UNICAM_SYN BIT(0)
> +#define UNICAM_CS BIT(1)
> +#define UNICAM_SBE BIT(2)
> +#define UNICAM_PBE BIT(3)
> +#define UNICAM_HOE BIT(4)
> +#define UNICAM_PLE BIT(5)
> +#define UNICAM_SSC BIT(6)
> +#define UNICAM_CRCE BIT(7)
> +#define UNICAM_OES BIT(8)
> +#define UNICAM_IFO BIT(9)
> +#define UNICAM_OFO BIT(10)
> +#define UNICAM_BFO BIT(11)
> +#define UNICAM_DL BIT(12)
> +#define UNICAM_PS BIT(13)
> +#define UNICAM_IS BIT(14)
> +#define UNICAM_PI0 BIT(15)
> +#define UNICAM_PI1 BIT(16)
> +#define UNICAM_FSI_S BIT(17)
> +#define UNICAM_FEI_S BIT(18)
> +#define UNICAM_LCI_S BIT(19)
> +#define UNICAM_BUF0_RDY BIT(20)
> +#define UNICAM_BUF0_NO BIT(21)
> +#define UNICAM_BUF1_RDY BIT(22)
> +#define UNICAM_BUF1_NO BIT(23)
> +#define UNICAM_DI BIT(24)
> +
> +#define UNICAM_STA_MASK_ALL \
> + (UNICAM_DL | \
> + UNICAM_SBE | \
> + UNICAM_PBE | \
> + UNICAM_HOE | \
> + UNICAM_PLE | \
> + UNICAM_SSC | \
> + UNICAM_CRCE | \
> + UNICAM_IFO | \
> + UNICAM_OFO | \
> + UNICAM_PS | \
> + UNICAM_PI0 | \
> + UNICAM_PI1)
> +
> +/* UNICAM_ANA Register */
> +#define UNICAM_APD BIT(0)
> +#define UNICAM_BPD BIT(1)
> +#define UNICAM_AR BIT(2)
> +#define UNICAM_DDL BIT(3)
> +#define UNICAM_CTATADJ_MASK GENMASK(7, 4)
> +#define UNICAM_PTATADJ_MASK GENMASK(11, 8)
> +
> +/* UNICAM_PRI Register */
> +#define UNICAM_PE BIT(0)
> +#define UNICAM_PT_MASK GENMASK(2, 1)
> +#define UNICAM_NP_MASK GENMASK(7, 4)
> +#define UNICAM_PP_MASK GENMASK(11, 8)
> +#define UNICAM_BS_MASK GENMASK(15, 12)
> +#define UNICAM_BL_MASK GENMASK(17, 16)
> +
> +/* UNICAM_CLK Register */
> +#define UNICAM_CLE BIT(0)
> +#define UNICAM_CLPD BIT(1)
> +#define UNICAM_CLLPE BIT(2)
> +#define UNICAM_CLHSE BIT(3)
> +#define UNICAM_CLTRE BIT(4)
> +#define UNICAM_CLAC_MASK GENMASK(8, 5)
> +#define UNICAM_CLSTE BIT(29)
> +
> +/* UNICAM_CLT Register */
> +#define UNICAM_CLT1_MASK GENMASK(7, 0)
> +#define UNICAM_CLT2_MASK GENMASK(15, 8)
> +
> +/* UNICAM_DATn Registers */
> +#define UNICAM_DLE BIT(0)
> +#define UNICAM_DLPD BIT(1)
> +#define UNICAM_DLLPE BIT(2)
> +#define UNICAM_DLHSE BIT(3)
> +#define UNICAM_DLTRE BIT(4)
> +#define UNICAM_DLSM BIT(5)
> +#define UNICAM_DLFO BIT(28)
> +#define UNICAM_DLSTE BIT(29)
> +
> +#define UNICAM_DAT_MASK_ALL (UNICAM_DLSTE | UNICAM_DLFO)
> +
> +/* UNICAM_DLT Register */
> +#define UNICAM_DLT1_MASK GENMASK(7, 0)
> +#define UNICAM_DLT2_MASK GENMASK(15, 8)
> +#define UNICAM_DLT3_MASK GENMASK(23, 16)
> +
> +/* UNICAM_ICTL Register */
> +#define UNICAM_FSIE BIT(0)
> +#define UNICAM_FEIE BIT(1)
> +#define UNICAM_IBOB BIT(2)
> +#define UNICAM_FCM BIT(3)
> +#define UNICAM_TFC BIT(4)
> +#define UNICAM_LIP_MASK GENMASK(6, 5)
> +#define UNICAM_LCIE_MASK GENMASK(28, 16)
> +
> +/* UNICAM_IDI0/1 Register */
> +#define UNICAM_ID0_MASK GENMASK(7, 0)
> +#define UNICAM_ID1_MASK GENMASK(15, 8)
> +#define UNICAM_ID2_MASK GENMASK(23, 16)
> +#define UNICAM_ID3_MASK GENMASK(31, 24)
> +
> +/* UNICAM_ISTA Register */
> +#define UNICAM_FSI BIT(0)
> +#define UNICAM_FEI BIT(1)
> +#define UNICAM_LCI BIT(2)
> +
> +#define UNICAM_ISTA_MASK_ALL (UNICAM_FSI | UNICAM_FEI | UNICAM_LCI)
> +
> +/* UNICAM_IPIPE Register */
> +#define UNICAM_PUM_MASK GENMASK(2, 0)
> +/* Unpacking modes */
> +#define UNICAM_PUM_NONE 0
> +#define UNICAM_PUM_UNPACK6 1
> +#define UNICAM_PUM_UNPACK7 2
> +#define UNICAM_PUM_UNPACK8 3
> +#define UNICAM_PUM_UNPACK10 4
> +#define UNICAM_PUM_UNPACK12 5
> +#define UNICAM_PUM_UNPACK14 6
> +#define UNICAM_PUM_UNPACK16 7
> +#define UNICAM_DDM_MASK GENMASK(6, 3)
> +#define UNICAM_PPM_MASK GENMASK(9, 7)
> +/* Packing modes */
> +#define UNICAM_PPM_NONE 0
> +#define UNICAM_PPM_PACK8 1
> +#define UNICAM_PPM_PACK10 2
> +#define UNICAM_PPM_PACK12 3
> +#define UNICAM_PPM_PACK14 4
> +#define UNICAM_PPM_PACK16 5
> +#define UNICAM_DEM_MASK GENMASK(11, 10)
> +#define UNICAM_DEBL_MASK GENMASK(14, 12)
> +#define UNICAM_ICM_MASK GENMASK(16, 15)
> +#define UNICAM_IDM_MASK GENMASK(17, 17)
> +
> +/* UNICAM_ICC Register */
> +#define UNICAM_ICFL_MASK GENMASK(4, 0)
> +#define UNICAM_ICFH_MASK GENMASK(9, 5)
> +#define UNICAM_ICST_MASK GENMASK(12, 10)
> +#define UNICAM_ICLT_MASK GENMASK(15, 13)
> +#define UNICAM_ICLL_MASK GENMASK(31, 16)
> +
> +/* UNICAM_DCS Register */
> +#define UNICAM_DIE BIT(0)
> +#define UNICAM_DIM BIT(1)
> +#define UNICAM_DBOB BIT(3)
> +#define UNICAM_FDE BIT(4)
> +#define UNICAM_LDP BIT(5)
> +#define UNICAM_EDL_MASK GENMASK(15, 8)
> +
> +/* UNICAM_DBCTL Register */
> +#define UNICAM_DBEN BIT(0)
> +#define UNICAM_BUF0_IE BIT(1)
> +#define UNICAM_BUF1_IE BIT(2)
> +
> +/* UNICAM_CMP[0,1] register */
> +#define UNICAM_PCE BIT(31)
> +#define UNICAM_GI BIT(9)
> +#define UNICAM_CPH BIT(8)
> +#define UNICAM_PCVC_MASK GENMASK(7, 6)
> +#define UNICAM_PCDT_MASK GENMASK(5, 0)
> +
> +/* UNICAM_MISC register */
> +#define UNICAM_FL0 BIT(6)
> +#define UNICAM_FL1 BIT(9)
> +
> +#endif
> diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam.c b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> new file mode 100644
> index 000000000000..470e691637c7
> --- /dev/null
> +++ b/drivers/media/platform/bcm2835/bcm2835-unicam.c
> @@ -0,0 +1,2570 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * BCM283x / BCM271x Unicam Capture Driver
> + *
> + * Copyright (C) 2017-2020 - Raspberry Pi (Trading) Ltd.
> + *
> + * Dave Stevenson <[email protected]>
> + *
> + * Based on TI am437x driver by
> + * Benoit Parrot <[email protected]>
> + * Lad, Prabhakar <[email protected]>
> + *
> + * and TI CAL camera interface driver by
> + * Benoit Parrot <[email protected]>
> + *
> + *
> + * There are two camera drivers in the kernel for BCM283x - this one
> + * and bcm2835-camera (currently in staging).
> + *
> + * This driver directly controls the Unicam peripheral - there is no
> + * involvement with the VideoCore firmware. Unicam receives CSI-2 or
> + * CCP2 data and writes it into SDRAM.
> + * The only potential processing options are to repack Bayer data into an
> + * alternate format, and applying windowing.
> + * The repacking does not shift the data, so can repack V4L2_PIX_FMT_Sxxxx10P
> + * to V4L2_PIX_FMT_Sxxxx10, or V4L2_PIX_FMT_Sxxxx12P to V4L2_PIX_FMT_Sxxxx12,
> + * but not generically up to V4L2_PIX_FMT_Sxxxx16. The driver will add both
> + * formats where the relevant formats are defined, and will automatically
> + * configure the repacking as required.
> + * Support for windowing may be added later.
> + *
> + * It should be possible to connect this driver to any sensor with a
> + * suitable output interface and V4L2 subdevice driver.
> + *
> + * bcm2835-camera uses the VideoCore firmware to control the sensor,
> + * Unicam, ISP, and all tuner control loops. Fully processed frames are
> + * delivered to the driver by the firmware. It only has sensor drivers
> + * for Omnivision OV5647, and Sony IMX219 sensors.
> + *
> + * The two drivers are mutually exclusive for the same Unicam instance.
> + * The VideoCore firmware checks the device tree configuration during boot.
> + * If it finds device tree nodes called csi0 or csi1 it will block the
> + * firmware from accessing the peripheral, and bcm2835-camera will
> + * not be able to stream data.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of_device.h>
> +#include <linux/of_graph.h>

You won't need this one. Maybe init.h as well?

> +#include <linux/pinctrl/consumer.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>

And this one?

> +#include <linux/videodev2.h>
> +
> +#include <media/v4l2-async.h>
> +#include <media/v4l2-common.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-dev.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-dv-timings.h>

This, too?

> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-fwnode.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "bcm2835-unicam-regs.h"
> +
> +#define UNICAM_MODULE_NAME "unicam"
> +
> +/*
> + * Unicam must request a minimum of 250Mhz from the VPU clock.
> + * Otherwise the input FIFOs overrun and cause image corruption.
> + */
> +#define UNICAM_MIN_VPU_CLOCK_RATE (250 * 1000 * 1000)
> +
> +/*
> + * Stride is a 16 bit register, but also has to be a multiple of 32.
> + */
> +#define UNICAM_BPL_ALIGNMENT 32
> +#define UNICAM_MAX_BYTESPERLINE ((1 << 16) - UNICAM_BPL_ALIGNMENT)
> +/*
> + * Max width is therefore determined by the max stride divided by
> + * the number of bits per pixel. Take 32bpp as a
> + * worst case.
> + * No imposed limit on the height, so adopt a square image for want
> + * of anything better.
> + */
> +#define UNICAM_MAX_WIDTH (UNICAM_MAX_BYTESPERLINE / 4)
> +#define UNICAM_MAX_HEIGHT UNICAM_MAX_WIDTH
> +/* Define a nominal minimum image size */
> +#define UNICAM_MIN_WIDTH 16
> +#define UNICAM_MIN_HEIGHT 16
> +/* Default size of the embedded buffer */
> +#define UNICAM_EMBEDDED_SIZE 16384
> +
> +/*
> + * Size of the dummy buffer. Can be any size really, but the DMA
> + * allocation works in units of page sizes.
> + */
> +#define UNICAM_DUMMY_BUF_SIZE PAGE_SIZE
> +
> +#define UNICAM_SD_PAD_SINK 0
> +#define UNICAM_SD_PAD_SOURCE_IMAGE 1
> +#define UNICAM_SD_PAD_SOURCE_METADATA 2
> +#define UNICAM_SD_NUM_PADS (1 + UNICAM_SD_PAD_SOURCE_METADATA)
> +
> +static inline bool unicam_sd_pad_is_sink(u32 pad)
> +{
> + /* Camera RX has 1 sink pad, and N source pads */
> + return pad == 0;
> +}
> +
> +static inline bool unicam_sd_pad_is_source(u32 pad)
> +{
> + /* Camera RX has 1 sink pad, and N source pads */
> + return pad != UNICAM_SD_PAD_SINK;
> +}
> +
> +enum unicam_node_type {
> + UNICAM_IMAGE_NODE,
> + UNICAM_METADATA_NODE,
> + UNICAM_MAX_NODES
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * Structure definitions
> + */
> +
> +/*
> + * struct unicam_fmt - Unicam media bus format information
> + * @fourcc: V4L2 pixel format FCC identifier. 0 if n/a.
> + * @unpacked_fourcc: V4L2 pixel format FCC identifier if the data is expanded
> + * out to 16bpp. 0 if n/a.
> + * @code: V4L2 media bus format code.
> + * @depth: Bits per pixel as delivered from the source.
> + * @csi_dt: CSI data type.
> + * @metadata_fmt: This format only applies to the metadata pad.
> + */
> +struct unicam_fmt {
> + u32 fourcc;
> + u32 unpacked_fourcc;
> + u32 code;
> + u8 depth;
> + u8 csi_dt;
> + u8 metadata_fmt:1;
> +};
> +
> +static const struct unicam_fmt unicam_image_formats[] = {
> + /* YUV Formats */
> + {
> + .fourcc = V4L2_PIX_FMT_YUYV,
> + .code = MEDIA_BUS_FMT_YUYV8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + .fourcc = V4L2_PIX_FMT_UYVY,
> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + .fourcc = V4L2_PIX_FMT_YVYU,
> + .code = MEDIA_BUS_FMT_YVYU8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + .fourcc = V4L2_PIX_FMT_VYUY,
> + .code = MEDIA_BUS_FMT_VYUY8_1X16,
> + .depth = 16,
> + .csi_dt = 0x1e,
> + }, {
> + /* RGB Formats */
> + .fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */
> + .code = MEDIA_BUS_FMT_RGB565_1X16,
> + .depth = 16,
> + .csi_dt = 0x22,
> + }, {
> + .fourcc = V4L2_PIX_FMT_RGB24, /* rgb */
> + .code = MEDIA_BUS_FMT_RGB888_1X24,
> + .depth = 24,
> + .csi_dt = 0x24,
> + }, {
> + .fourcc = V4L2_PIX_FMT_BGR24, /* bgr */
> + .code = MEDIA_BUS_FMT_BGR888_1X24,
> + .depth = 24,
> + .csi_dt = 0x24,
> + }, {
> + /* Bayer Formats */
> + .fourcc = V4L2_PIX_FMT_SBGGR8,
> + .code = MEDIA_BUS_FMT_SBGGR8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG8,
> + .code = MEDIA_BUS_FMT_SGBRG8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG8,
> + .code = MEDIA_BUS_FMT_SGRBG8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB8,
> + .code = MEDIA_BUS_FMT_SRGGB8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SBGGR10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SBGGR10,
> + .code = MEDIA_BUS_FMT_SBGGR10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGBRG10,
> + .code = MEDIA_BUS_FMT_SGBRG10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGRBG10,
> + .code = MEDIA_BUS_FMT_SGRBG10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SRGGB10,
> + .code = MEDIA_BUS_FMT_SRGGB10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SBGGR12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SBGGR12,
> + .code = MEDIA_BUS_FMT_SBGGR12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGBRG12,
> + .code = MEDIA_BUS_FMT_SGBRG12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGRBG12,
> + .code = MEDIA_BUS_FMT_SGRBG12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SRGGB12,
> + .code = MEDIA_BUS_FMT_SRGGB12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SBGGR14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SBGGR14,
> + .code = MEDIA_BUS_FMT_SBGGR14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGBRG14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGBRG14,
> + .code = MEDIA_BUS_FMT_SGBRG14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SGRBG14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SGRBG14,
> + .code = MEDIA_BUS_FMT_SGRBG14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + .fourcc = V4L2_PIX_FMT_SRGGB14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_SRGGB14,
> + .code = MEDIA_BUS_FMT_SRGGB14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + }, {
> + /* 16 bit Bayer formats could be supported. */
> +
> + /* Greyscale formats */
> + .fourcc = V4L2_PIX_FMT_GREY,
> + .code = MEDIA_BUS_FMT_Y8_1X8,
> + .depth = 8,
> + .csi_dt = 0x2a,
> + }, {
> + .fourcc = V4L2_PIX_FMT_Y10P,
> + .unpacked_fourcc = V4L2_PIX_FMT_Y10,
> + .code = MEDIA_BUS_FMT_Y10_1X10,
> + .depth = 10,
> + .csi_dt = 0x2b,
> + }, {
> + .fourcc = V4L2_PIX_FMT_Y12P,
> + .unpacked_fourcc = V4L2_PIX_FMT_Y12,
> + .code = MEDIA_BUS_FMT_Y12_1X12,
> + .depth = 12,
> + .csi_dt = 0x2c,
> + }, {
> + .fourcc = V4L2_PIX_FMT_Y14P,
> + .unpacked_fourcc = V4L2_PIX_FMT_Y14,
> + .code = MEDIA_BUS_FMT_Y14_1X14,
> + .depth = 14,
> + .csi_dt = 0x2d,
> + },
> +};
> +
> +static const struct unicam_fmt unicam_meta_formats[] = {
> + {
> + .fourcc = V4L2_META_FMT_8,
> + .code = MEDIA_BUS_FMT_METADATA_8,
> + .depth = 8,
> + .metadata_fmt = 1,
> + }
> +};
> +
> +static const struct v4l2_mbus_framefmt default_image_format = {
> + .width = 640,
> + .height = 480,
> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> + .field = V4L2_FIELD_NONE,
> + .colorspace = V4L2_COLORSPACE_SRGB,
> + .ycbcr_enc = V4L2_YCBCR_ENC_601,
> + .quantization = V4L2_QUANTIZATION_LIM_RANGE,
> + .xfer_func = V4L2_XFER_FUNC_SRGB,
> + .flags = 0,
> +};
> +
> +struct unicam_buffer {
> + struct vb2_v4l2_buffer vb;
> + struct list_head list;
> +};
> +
> +static inline struct unicam_buffer *to_unicam_buffer(struct vb2_buffer *vb)
> +{
> + return container_of(vb, struct unicam_buffer, vb.vb2_buf);
> +}
> +
> +struct unicam_node {
> + bool registered;
> + bool streaming;
> + unsigned int id;
> +
> + /* Pointer to the current v4l2_buffer */
> + struct unicam_buffer *cur_frm;
> + /* Pointer to the next v4l2_buffer */
> + struct unicam_buffer *next_frm;
> + /* video capture */
> + const struct unicam_fmt *fmt;
> + /* Used to store current pixel format */
> + struct v4l2_format v_fmt;
> + /* Buffer queue used in video-buf */
> + struct vb2_queue buffer_queue;
> + /* Queue of filled frames */
> + struct list_head dma_queue;
> + /* IRQ lock for DMA queue */
> + spinlock_t dma_queue_lock;
> + /* lock used to access this structure */
> + struct mutex lock;
> + /* Identifies video device for this channel */
> + struct video_device video_dev;
> + /* Pointer to the parent handle */
> + struct unicam_device *dev;
> + struct media_pad pad;
> + /*
> + * Dummy buffer intended to be used by unicam
> + * if we have no other queued buffers to swap to.
> + */
> + void *dummy_buf_cpu_addr;
> + dma_addr_t dummy_buf_dma_addr;
> +};
> +
> +struct unicam_device {
> + struct kref kref;
> +
> + /* peripheral base address */
> + void __iomem *base;
> + /* clock gating base address */
> + void __iomem *clk_gate_base;
> + /* lp clock handle */
> + struct clk *clock;
> + /* vpu clock handle */
> + struct clk *vpu_clock;
> + /* V4l2 device */
> + struct v4l2_device v4l2_dev;
> + struct media_device mdev;
> +
> + /* parent device */
> + struct device *dev;
> + /* subdevice async Notifier */
> + struct v4l2_async_notifier notifier;
> + unsigned int sequence;
> +
> + /* Sensor node */
> + struct {
> + struct v4l2_subdev *subdev;
> + struct media_pad *pad;
> + } sensor;
> +
> + /* Internal subdev */
> + struct {
> + struct v4l2_subdev sd;
> + struct media_pad pads[UNICAM_SD_NUM_PADS];
> + bool streaming;
> + } subdev;
> +
> + enum v4l2_mbus_type bus_type;
> + /*
> + * Stores bus.mipi_csi2.flags for CSI2 sensors, or
> + * bus.mipi_csi1.strobe for CCP2.
> + */
> + unsigned int bus_flags;
> + unsigned int max_data_lanes;
> + unsigned int active_data_lanes;
> +
> + struct media_pipeline pipe;
> +
> + struct unicam_node node[UNICAM_MAX_NODES];
> +};
> +
> +static inline struct unicam_device *
> +notifier_to_unicam_device(struct v4l2_async_notifier *notifier)
> +{
> + return container_of(notifier, struct unicam_device, notifier);
> +}
> +
> +static inline struct unicam_device *
> +sd_to_unicam_device(struct v4l2_subdev *sd)
> +{
> + return container_of(sd, struct unicam_device, subdev.sd);
> +}
> +
> +static void unicam_release(struct kref *kref)
> +{
> + struct unicam_device *unicam =
> + container_of(kref, struct unicam_device, kref);
> +
> + if (unicam->mdev.dev)
> + media_device_cleanup(&unicam->mdev);
> +
> + kfree(unicam);
> +}
> +
> +static void unicam_put(struct unicam_device *unicam)
> +{
> + kref_put(&unicam->kref, unicam_release);
> +}
> +
> +static struct unicam_device *unicam_get(struct unicam_device *unicam)
> +{
> + kref_get(&unicam->kref);
> + return unicam;
> +}
> +
> +static void unicam_node_release(struct video_device *vdev)
> +{
> + struct unicam_node *node = video_get_drvdata(vdev);
> +
> + unicam_put(node->dev);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Misc helper functions
> + */
> +
> +static inline bool is_metadata_node(struct unicam_node *node)
> +{
> + return node->video_dev.device_caps & V4L2_CAP_META_CAPTURE;
> +}
> +
> +static inline bool is_image_node(struct unicam_node *node)
> +{
> + return node->video_dev.device_caps & V4L2_CAP_VIDEO_CAPTURE;
> +}
> +
> +/* Hardware access */
> +static inline void unicam_clk_write(struct unicam_device *unicam, u32 val)
> +{
> + /* Pass the CM_PASSWORD along with the value. */
> + writel(val | 0x5a000000, unicam->clk_gate_base);
> +}
> +
> +static inline u32 unicam_reg_read(struct unicam_device *unicam, u32 offset)
> +{
> + return readl(unicam->base + offset);
> +}
> +
> +static inline void unicam_reg_write(struct unicam_device *unicam, u32 offset, u32 val)
> +{
> + writel(val, unicam->base + offset);
> +}
> +
> +static inline int unicam_get_field(u32 value, u32 mask)
> +{
> + return (value & mask) >> __ffs(mask);
> +}
> +
> +static inline void unicam_set_field(u32 *valp, u32 field, u32 mask)
> +{
> + u32 val = *valp;
> +
> + val &= ~mask;
> + val |= (field << __ffs(mask)) & mask;
> + *valp = val;
> +}
> +
> +static inline void unicam_reg_write_field(struct unicam_device *unicam, u32 offset,
> + u32 field, u32 mask)
> +{
> + u32 val = unicam_reg_read(unicam, offset);
> +
> + unicam_set_field(&val, field, mask);
> + unicam_reg_write(unicam, offset, val);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Format data table and helper functions
> + */
> +
> +/* Format setup functions */
> +static const struct unicam_fmt *unicam_find_format_by_code(u32 code, u32 pad)
> +{
> + const struct unicam_fmt *formats;
> + unsigned int num_formats;
> + unsigned int i;
> +
> + if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + formats = unicam_image_formats;
> + num_formats = ARRAY_SIZE(unicam_image_formats);
> + } else {
> + formats = unicam_meta_formats;
> + num_formats = ARRAY_SIZE(unicam_meta_formats);
> + }
> +
> + for (i = 0; i < num_formats; i++) {
> + if (formats[i].code == code)
> + return &formats[i];
> + }
> +
> + return NULL;
> +}
> +
> +static const struct unicam_fmt *unicam_find_format_by_fourcc(u32 fourcc, u32 pad)
> +{
> + const struct unicam_fmt *formats;
> + unsigned int num_formats;
> + unsigned int i;
> +
> + if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + formats = unicam_image_formats;
> + num_formats = ARRAY_SIZE(unicam_image_formats);
> + } else {
> + formats = unicam_meta_formats;
> + num_formats = ARRAY_SIZE(unicam_meta_formats);
> + }
> +
> + for (i = 0; i < num_formats; ++i) {
> + if (formats[i].fourcc == fourcc)
> + return &formats[i];
> + }
> +
> + return NULL;
> +}
> +
> +static int unicam_calc_format_size_bpl(struct unicam_device *unicam,
> + const struct unicam_fmt *fmt,
> + struct v4l2_format *f)
> +{
> + u32 min_bpl;
> +
> + v4l_bound_align_image(&f->fmt.pix.width, UNICAM_MIN_WIDTH,
> + UNICAM_MAX_WIDTH, 2,
> + &f->fmt.pix.height, UNICAM_MIN_HEIGHT,
> + UNICAM_MAX_HEIGHT, 0, 0);
> +
> + /* Unpacking always goes to 16bpp */
> + if (f->fmt.pix.pixelformat == fmt->unpacked_fourcc)
> + min_bpl = f->fmt.pix.width * 2;
> + else
> + min_bpl = f->fmt.pix.width * fmt->depth / 8;
> + min_bpl = ALIGN(min_bpl, UNICAM_BPL_ALIGNMENT);
> +
> + f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline,
> + UNICAM_BPL_ALIGNMENT);
> + f->fmt.pix.bytesperline = clamp_t(unsigned int,
> + f->fmt.pix.bytesperline,
> + min_bpl,
> + UNICAM_MAX_BYTESPERLINE);
> +
> + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
> +
> + dev_dbg(unicam->dev, "%s: fourcc: %08X size: %ux%u bpl:%u img_size:%u\n",
> + __func__,
> + f->fmt.pix.pixelformat,
> + f->fmt.pix.width, f->fmt.pix.height,
> + f->fmt.pix.bytesperline, f->fmt.pix.sizeimage);
> +
> + return 0;
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Hardware handling
> + */
> +
> +static void unicam_wr_dma_addr(struct unicam_node *node, dma_addr_t dmaaddr,
> + unsigned int buffer_size)
> +{
> + dma_addr_t endaddr = dmaaddr + buffer_size;
> +
> + if (node->id == UNICAM_IMAGE_NODE) {
> + unicam_reg_write(node->dev, UNICAM_IBSA0, dmaaddr);
> + unicam_reg_write(node->dev, UNICAM_IBEA0, endaddr);
> + } else {
> + unicam_reg_write(node->dev, UNICAM_DBSA0, dmaaddr);
> + unicam_reg_write(node->dev, UNICAM_DBEA0, endaddr);
> + }
> +}
> +
> +static unsigned int unicam_get_lines_done(struct unicam_device *unicam)
> +{
> + dma_addr_t start_addr, cur_addr;
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> + unsigned int stride = node->v_fmt.fmt.pix.bytesperline;
> + struct unicam_buffer *frm = node->cur_frm;
> +
> + if (!frm)
> + return 0;
> +
> + start_addr = vb2_dma_contig_plane_dma_addr(&frm->vb.vb2_buf, 0);
> + cur_addr = unicam_reg_read(unicam, UNICAM_IBWP);
> + return (unsigned int)(cur_addr - start_addr) / stride;
> +}
> +
> +static void unicam_schedule_next_buffer(struct unicam_node *node)
> +{
> + struct unicam_buffer *buf;
> + unsigned int size;
> + dma_addr_t addr;
> +
> + buf = list_first_entry(&node->dma_queue, struct unicam_buffer, list);
> + node->next_frm = buf;
> + list_del(&buf->list);
> +
> + addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> + if (is_image_node(node))
> + size = node->v_fmt.fmt.pix.sizeimage;
> + else
> + size = node->v_fmt.fmt.meta.buffersize;
> +
> + unicam_wr_dma_addr(node, addr, size);
> +}
> +
> +static void unicam_schedule_dummy_buffer(struct unicam_node *node)
> +{
> + struct unicam_device *unicam = node->dev;
> + int node_id = is_image_node(node) ? UNICAM_IMAGE_NODE : UNICAM_METADATA_NODE;
> +
> + dev_dbg(unicam->dev, "Scheduling dummy buffer for node %d\n", node_id);
> +
> + unicam_wr_dma_addr(node, node->dummy_buf_dma_addr,
> + UNICAM_DUMMY_BUF_SIZE);
> +
> + node->next_frm = NULL;
> +}
> +
> +static void unicam_process_buffer_complete(struct unicam_node *node,
> + unsigned int sequence)
> +{
> + node->cur_frm->vb.field = node->v_fmt.fmt.pix.field;
> + node->cur_frm->vb.sequence = sequence;
> +
> + vb2_buffer_done(&node->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE);
> +}
> +
> +static void unicam_queue_event_sof(struct unicam_device *unicam)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> +
> + struct v4l2_event event = {
> + .type = V4L2_EVENT_FRAME_SYNC,
> + .u.frame_sync.frame_sequence = unicam->sequence,
> + };
> +
> + v4l2_event_queue(&node->video_dev, &event);
> +}
> +
> +/*
> + * unicam_isr : ISR handler for unicam capture
> + * @irq: irq number
> + * @dev_id: dev_id ptr
> + *
> + * It changes status of the captured buffer, takes next buffer from the queue
> + * and sets its address in unicam registers
> + */
> +static irqreturn_t unicam_isr(int irq, void *dev)
> +{
> + struct unicam_device *unicam = dev;
> + unsigned int lines_done = unicam_get_lines_done(dev);
> + unsigned int sequence = unicam->sequence;
> + unsigned int i;
> + u32 ista, sta;
> + bool fe;
> + u64 ts;
> +
> + sta = unicam_reg_read(unicam, UNICAM_STA);
> + /* Write value back to clear the interrupts */
> + unicam_reg_write(unicam, UNICAM_STA, sta);
> +
> + ista = unicam_reg_read(unicam, UNICAM_ISTA);
> + /* Write value back to clear the interrupts */
> + unicam_reg_write(unicam, UNICAM_ISTA, ista);
> +
> + dev_dbg(unicam->dev, "ISR: ISTA: 0x%X, STA: 0x%X, sequence %d, lines done %d",
> + ista, sta, sequence, lines_done);
> +
> + if (!(sta & (UNICAM_IS | UNICAM_PI0)))
> + return IRQ_HANDLED;
> +
> + /*
> + * Look for either the Frame End interrupt or the Packet Capture status
> + * to signal a frame end.
> + */
> + fe = ista & UNICAM_FEI || sta & UNICAM_PI0;
> +
> + /*
> + * We must run the frame end handler first. If we have a valid next_frm
> + * and we get a simultaneout FE + FS interrupt, running the FS handler
> + * first would null out the next_frm ptr and we would have lost the
> + * buffer forever.
> + */
> + if (fe) {
> + /*
> + * Ensure we have swapped buffers already as we can't
> + * stop the peripheral. If no buffer is available, use a
> + * dummy buffer to dump out frames until we get a new buffer
> + * to use.
> + */
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + if (!unicam->node[i].streaming)
> + continue;
> +
> + /*
> + * If cur_frm == next_frm, it means we have not had
> + * a chance to swap buffers, likely due to having
> + * multiple interrupts occurring simultaneously (like FE
> + * + FS + LS). In this case, we cannot signal the buffer
> + * as complete, as the HW will reuse that buffer.
> + */
> + if (unicam->node[i].cur_frm &&
> + unicam->node[i].cur_frm != unicam->node[i].next_frm)
> + unicam_process_buffer_complete(&unicam->node[i],
> + sequence);
> + unicam->node[i].cur_frm = unicam->node[i].next_frm;
> + }
> + unicam->sequence++;
> + }
> +
> + if (ista & UNICAM_FSI) {
> + /*
> + * Timestamp is to be when the first data byte was captured,
> + * aka frame start.
> + */
> + ts = ktime_get_ns();
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + if (!unicam->node[i].streaming)
> + continue;
> +
> + if (unicam->node[i].cur_frm)
> + unicam->node[i].cur_frm->vb.vb2_buf.timestamp =
> + ts;
> + else
> + dev_dbg(unicam->v4l2_dev.dev,
> + "ISR: [%d] Dropping frame, buffer not available at FS\n",
> + i);
> + /*
> + * Set the next frame output to go to a dummy frame
> + * if we have not managed to obtain another frame
> + * from the queue.
> + */
> + unicam_schedule_dummy_buffer(&unicam->node[i]);
> + }
> +
> + unicam_queue_event_sof(unicam);
> + }
> +
> + /*
> + * Cannot swap buffer at frame end, there may be a race condition
> + * where the HW does not actually swap it if the new frame has
> + * already started.
> + */
> + if (ista & (UNICAM_FSI | UNICAM_LCI) && !fe) {
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + if (!unicam->node[i].streaming)
> + continue;
> +
> + spin_lock(&unicam->node[i].dma_queue_lock);
> + if (!list_empty(&unicam->node[i].dma_queue) &&
> + !unicam->node[i].next_frm)
> + unicam_schedule_next_buffer(&unicam->node[i]);
> + spin_unlock(&unicam->node[i].dma_queue_lock);
> + }
> + }
> +
> + if (unicam_reg_read(unicam, UNICAM_ICTL) & UNICAM_FCM) {
> + /* Switch out of trigger mode if selected */
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_TFC);
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 0, UNICAM_FCM);
> + }
> + return IRQ_HANDLED;
> +}
> +
> +static void unicam_set_packing_config(struct unicam_device *unicam)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> + u32 pack, unpack;
> + u32 val;
> +
> + if (node->v_fmt.fmt.pix.pixelformat ==
> + node->fmt->fourcc) {
> + unpack = UNICAM_PUM_NONE;
> + pack = UNICAM_PPM_NONE;
> + } else {
> + switch (node->fmt->depth) {
> + case 8:
> + unpack = UNICAM_PUM_UNPACK8;
> + break;
> + case 10:
> + unpack = UNICAM_PUM_UNPACK10;
> + break;
> + case 12:
> + unpack = UNICAM_PUM_UNPACK12;
> + break;
> + case 14:
> + unpack = UNICAM_PUM_UNPACK14;
> + break;
> + case 16:
> + unpack = UNICAM_PUM_UNPACK16;
> + break;
> + default:
> + unpack = UNICAM_PUM_NONE;
> + break;
> + }
> +
> + /* Repacking is always to 16bpp */
> + pack = UNICAM_PPM_PACK16;
> + }
> +
> + val = 0;
> + unicam_set_field(&val, unpack, UNICAM_PUM_MASK);
> + unicam_set_field(&val, pack, UNICAM_PPM_MASK);
> + unicam_reg_write(unicam, UNICAM_IPIPE, val);
> +}
> +
> +static void unicam_cfg_image_id(struct unicam_device *unicam)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> +
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + /* CSI2 mode, hardcode VC 0 for now. */
> + unicam_reg_write(unicam, UNICAM_IDI0,
> + (0 << 6) | node->fmt->csi_dt);
> + } else {
> + /* CCP2 mode */
> + unicam_reg_write(unicam, UNICAM_IDI0,
> + 0x80 | node->fmt->csi_dt);
> + }
> +}
> +
> +static void unicam_enable_ed(struct unicam_device *unicam)
> +{
> + u32 val = unicam_reg_read(unicam, UNICAM_DCS);
> +
> + unicam_set_field(&val, 2, UNICAM_EDL_MASK);
> + /* Do not wrap at the end of the embedded data buffer */
> + unicam_set_field(&val, 0, UNICAM_DBOB);
> +
> + unicam_reg_write(unicam, UNICAM_DCS, val);
> +}
> +
> +static void unicam_start_rx(struct unicam_device *unicam, dma_addr_t *addr)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_IMAGE_NODE];
> + int line_int_freq = node->v_fmt.fmt.pix.height >> 2;
> + unsigned int size, i;
> + u32 val;
> +
> + if (line_int_freq < 128)
> + line_int_freq = 128;
> +
> + /* Enable lane clocks */
> + val = 1;
> + for (i = 0; i < unicam->active_data_lanes; i++)
> + val = val << 2 | 1;
> + unicam_clk_write(unicam, val);
> +
> + /* Basic init */
> + unicam_reg_write(unicam, UNICAM_CTRL, UNICAM_MEM);
> +
> + /* Enable analogue control, and leave in reset. */
> + val = UNICAM_AR;
> + unicam_set_field(&val, 7, UNICAM_CTATADJ_MASK);
> + unicam_set_field(&val, 7, UNICAM_PTATADJ_MASK);
> + unicam_reg_write(unicam, UNICAM_ANA, val);
> + usleep_range(1000, 2000);
> +
> + /* Come out of reset */
> + unicam_reg_write_field(unicam, UNICAM_ANA, 0, UNICAM_AR);
> +
> + /* Peripheral reset */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPR);
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPR);
> +
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPE);
> +
> + /* Enable Rx control. */
> + val = unicam_reg_read(unicam, UNICAM_CTRL);
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + unicam_set_field(&val, UNICAM_CPM_CSI2, UNICAM_CPM_MASK);
> + unicam_set_field(&val, UNICAM_DCM_STROBE, UNICAM_DCM_MASK);
> + } else {
> + unicam_set_field(&val, UNICAM_CPM_CCP2, UNICAM_CPM_MASK);
> + unicam_set_field(&val, unicam->bus_flags, UNICAM_DCM_MASK);

Just noting bus_flags field is a single bit telling whether to use strobe
mode or not. If this does the right thing here, that's fine then.

> + }
> + /* Packet framer timeout */
> + unicam_set_field(&val, 0xf, UNICAM_PFT_MASK);
> + unicam_set_field(&val, 128, UNICAM_OET_MASK);
> + unicam_reg_write(unicam, UNICAM_CTRL, val);
> +
> + unicam_reg_write(unicam, UNICAM_IHWIN, 0);
> + unicam_reg_write(unicam, UNICAM_IVWIN, 0);
> +
> + /* AXI bus access QoS setup */
> + val = unicam_reg_read(unicam, UNICAM_PRI);
> + unicam_set_field(&val, 0, UNICAM_BL_MASK);
> + unicam_set_field(&val, 0, UNICAM_BS_MASK);
> + unicam_set_field(&val, 0xe, UNICAM_PP_MASK);
> + unicam_set_field(&val, 8, UNICAM_NP_MASK);
> + unicam_set_field(&val, 2, UNICAM_PT_MASK);
> + unicam_set_field(&val, 1, UNICAM_PE);
> + unicam_reg_write(unicam, UNICAM_PRI, val);
> +
> + unicam_reg_write_field(unicam, UNICAM_ANA, 0, UNICAM_DDL);
> +
> + /* Always start in trigger frame capture mode (UNICAM_FCM set) */
> + val = UNICAM_FSIE | UNICAM_FEIE | UNICAM_FCM | UNICAM_IBOB;
> + unicam_set_field(&val, line_int_freq, UNICAM_LCIE_MASK);
> + unicam_reg_write(unicam, UNICAM_ICTL, val);
> + unicam_reg_write(unicam, UNICAM_STA, UNICAM_STA_MASK_ALL);
> + unicam_reg_write(unicam, UNICAM_ISTA, UNICAM_ISTA_MASK_ALL);
> +
> + /* tclk_term_en */
> + unicam_reg_write_field(unicam, UNICAM_CLT, 2, UNICAM_CLT1_MASK);
> + /* tclk_settle */
> + unicam_reg_write_field(unicam, UNICAM_CLT, 6, UNICAM_CLT2_MASK);
> + /* td_term_en */
> + unicam_reg_write_field(unicam, UNICAM_DLT, 2, UNICAM_DLT1_MASK);
> + /* ths_settle */
> + unicam_reg_write_field(unicam, UNICAM_DLT, 6, UNICAM_DLT2_MASK);
> + /* trx_enable */
> + unicam_reg_write_field(unicam, UNICAM_DLT, 0, UNICAM_DLT3_MASK);
> +
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_SOE);
> +
> + /* Packet compare setup - required to avoid missing frame ends */
> + val = 0;
> + unicam_set_field(&val, 1, UNICAM_PCE);
> + unicam_set_field(&val, 1, UNICAM_GI);
> + unicam_set_field(&val, 1, UNICAM_CPH);
> + unicam_set_field(&val, 0, UNICAM_PCVC_MASK);
> + unicam_set_field(&val, 1, UNICAM_PCDT_MASK);
> + unicam_reg_write(unicam, UNICAM_CMP0, val);
> +
> + /* Enable clock lane and set up terminations */
> + val = 0;
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + /* CSI2 */
> + unicam_set_field(&val, 1, UNICAM_CLE);
> + unicam_set_field(&val, 1, UNICAM_CLLPE);
> + if (unicam->bus_flags & V4L2_MBUS_CSI2_CONTINUOUS_CLOCK) {
> + unicam_set_field(&val, 1, UNICAM_CLTRE);
> + unicam_set_field(&val, 1, UNICAM_CLHSE);
> + }
> + } else {
> + /* CCP2 */
> + unicam_set_field(&val, 1, UNICAM_CLE);
> + unicam_set_field(&val, 1, UNICAM_CLHSE);
> + unicam_set_field(&val, 1, UNICAM_CLTRE);
> + }
> + unicam_reg_write(unicam, UNICAM_CLK, val);
> +
> + /*
> + * Enable required data lanes with appropriate terminations.
> + * The same value needs to be written to UNICAM_DATn registers for
> + * the active lanes, and 0 for inactive ones.
> + */
> + val = 0;
> + if (unicam->bus_type == V4L2_MBUS_CSI2_DPHY) {
> + /* CSI2 */
> + unicam_set_field(&val, 1, UNICAM_DLE);
> + unicam_set_field(&val, 1, UNICAM_DLLPE);
> + if (unicam->bus_flags & V4L2_MBUS_CSI2_CONTINUOUS_CLOCK) {
> + unicam_set_field(&val, 1, UNICAM_DLTRE);
> + unicam_set_field(&val, 1, UNICAM_DLHSE);
> + }
> + } else {
> + /* CCP2 */
> + unicam_set_field(&val, 1, UNICAM_DLE);
> + unicam_set_field(&val, 1, UNICAM_DLHSE);
> + unicam_set_field(&val, 1, UNICAM_DLTRE);
> + }
> + unicam_reg_write(unicam, UNICAM_DAT0, val);
> +
> + if (unicam->active_data_lanes == 1)
> + val = 0;
> + unicam_reg_write(unicam, UNICAM_DAT1, val);
> +
> + if (unicam->max_data_lanes > 2) {
> + /*
> + * Registers UNICAM_DAT2 and UNICAM_DAT3 only valid if the
> + * instance supports more than 2 data lanes.
> + */
> + if (unicam->active_data_lanes == 2)
> + val = 0;
> + unicam_reg_write(unicam, UNICAM_DAT2, val);
> +
> + if (unicam->active_data_lanes == 3)
> + val = 0;
> + unicam_reg_write(unicam, UNICAM_DAT3, val);
> + }
> +
> + unicam_reg_write(unicam, UNICAM_IBLS,
> + node->v_fmt.fmt.pix.bytesperline);
> + size = node->v_fmt.fmt.pix.sizeimage;
> + unicam_wr_dma_addr(&unicam->node[UNICAM_IMAGE_NODE],
> + addr[UNICAM_IMAGE_NODE], size);
> + unicam_set_packing_config(unicam);
> + unicam_cfg_image_id(unicam);
> +
> + val = unicam_reg_read(unicam, UNICAM_MISC);
> + unicam_set_field(&val, 1, UNICAM_FL0);
> + unicam_set_field(&val, 1, UNICAM_FL1);
> + unicam_reg_write(unicam, UNICAM_MISC, val);
> +
> + /* Enable peripheral */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPE);
> +
> + /* Load image pointers */
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_LIP_MASK);
> +
> + /*
> + * Enable trigger only for the first frame to
> + * sync correctly to the FS from the source.
> + */
> + unicam_reg_write_field(unicam, UNICAM_ICTL, 1, UNICAM_TFC);
> +}
> +
> +static void unicam_start_metadata(struct unicam_device *unicam, dma_addr_t *addr)
> +{
> + struct unicam_node *node = &unicam->node[UNICAM_METADATA_NODE];
> + unsigned int size;
> +
> + dev_dbg(unicam->dev, "enable metadata dma\n");
> + size = node->v_fmt.fmt.meta.buffersize;
> + unicam_enable_ed(unicam);
> + unicam_wr_dma_addr(node, addr[UNICAM_METADATA_NODE], size);
> + unicam_reg_write_field(unicam, UNICAM_DCS, 1, UNICAM_LDP);
> +}
> +
> +static void unicam_disable(struct unicam_device *unicam)
> +{
> + /* Analogue lane control disable */
> + unicam_reg_write_field(unicam, UNICAM_ANA, 1, UNICAM_DDL);
> +
> + /* Stop the output engine */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_SOE);
> +
> + /* Disable the data lanes. */
> + unicam_reg_write(unicam, UNICAM_DAT0, 0);
> + unicam_reg_write(unicam, UNICAM_DAT1, 0);
> +
> + if (unicam->max_data_lanes > 2) {
> + unicam_reg_write(unicam, UNICAM_DAT2, 0);
> + unicam_reg_write(unicam, UNICAM_DAT3, 0);
> + }
> +
> + /* Peripheral reset */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 1, UNICAM_CPR);
> + usleep_range(50, 100);
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPR);
> +
> + /* Disable peripheral */
> + unicam_reg_write_field(unicam, UNICAM_CTRL, 0, UNICAM_CPE);
> +
> + /* Clear ED setup */
> + unicam_reg_write(unicam, UNICAM_DCS, 0);
> +
> + /* Disable all lane clocks */
> + unicam_clk_write(unicam, 0);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 subdev Operations
> + */
> +
> +static int __unicam_subdev_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_krouting *routing)
> +{
> + int ret;
> +
> + ret = v4l2_subdev_routing_validate_1_to_1(routing);
> + if (ret)
> + return ret;
> +
> + v4l2_subdev_lock_state(state);
> +
> + ret = v4l2_subdev_set_routing(sd, state, routing);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_subdev_set_routing(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + enum v4l2_subdev_format_whence which,
> + struct v4l2_subdev_krouting *routing)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> +
> + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && unicam->subdev.streaming)
> + return -EBUSY;
> +
> + return __unicam_subdev_set_routing(sd, state, routing);
> +}
> +
> +static int unicam_subdev_init_cfg(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state)
> +{
> + struct v4l2_subdev_route routes[] = {
> + {
> + .sink_pad = UNICAM_SD_PAD_SINK,
> + .sink_stream = 0,
> + .source_pad = UNICAM_SD_PAD_SOURCE_IMAGE,
> + .source_stream = 0,
> + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> + },
> + };
> +
> + struct v4l2_subdev_krouting routing = {
> + .num_routes = ARRAY_SIZE(routes),
> + .routes = routes,
> + };
> +
> + /* Initialize routing to single route to the fist source pad */
> + return __unicam_subdev_set_routing(sd, state, &routing);
> +}
> +
> +static int unicam_subdev_enum_mbus_code(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_mbus_code_enum *code)
> +{
> + u32 pad, stream;
> + int ret = -EINVAL;
> +
> + v4l2_subdev_lock_state(state);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, code->pad,
> + code->stream, &pad,
> + &stream);
> + if (ret)
> + goto out;
> +
> + if (unicam_sd_pad_is_source(code->pad)) {
> + /* No transcoding, source and sink codes must match. */
> + struct v4l2_mbus_framefmt *fmt;
> +
> + fmt = v4l2_state_get_stream_format(state, pad, stream);
> + if (!fmt)
> + goto out;
> +
> + if (code->index > 0)
> + goto out;
> +
> + code->code = fmt->code;
> + } else {
> + const struct unicam_fmt *formats;
> + unsigned int num_formats;
> +
> + if (pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + formats = unicam_image_formats;
> + num_formats = ARRAY_SIZE(unicam_image_formats);
> + } else {
> + formats = unicam_meta_formats;
> + num_formats = ARRAY_SIZE(unicam_meta_formats);
> + }
> +
> + if (code->index >= num_formats)
> + goto out;
> +
> + code->code = formats[code->index].code;
> + }
> +
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_sd_enable_streams(struct v4l2_subdev *sd, u32 pad,
> + u64 streams_mask)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_subdev_state *state;
> + u32 other_pad, other_stream;
> + int ret;
> +
> + if (WARN_ON(streams_mask != 1))
> + return -EINVAL;
> +
> + state = v4l2_subdev_lock_active_state(sd);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
> + &other_pad, &other_stream);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + if (ret)
> + return ret;
> +
> + unicam->sequence = 0;
> +
> + dev_dbg(unicam->dev, "Running with %u data lanes\n",
> + unicam->active_data_lanes);
> +
> + ret = clk_set_min_rate(unicam->vpu_clock, UNICAM_MIN_VPU_CLOCK_RATE);
> + if (ret) {
> + dev_err(unicam->dev, "failed to set up VPU clock\n");
> + return ret;
> + }
> +
> + ret = clk_prepare_enable(unicam->vpu_clock);
> + if (ret) {
> + dev_err(unicam->dev, "Failed to enable VPU clock: %d\n", ret);
> + goto err_vpu_clock;
> + }
> +
> + ret = clk_set_rate(unicam->clock, 100 * 1000 * 1000);
> + if (ret) {
> + dev_err(unicam->dev, "failed to set up CSI clock\n");
> + goto err_vpu_prepare;
> + }
> +
> + ret = clk_prepare_enable(unicam->clock);
> + if (ret) {
> + dev_err(unicam->dev, "Failed to enable CSI clock: %d\n", ret);
> + goto err_vpu_prepare;
> + }
> +
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, enable_streams,
> + unicam->sensor.pad->index, BIT(other_stream));
> +
> + if (ret && ret == -ENOIOCTLCMD)
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, s_stream, 1);
> +
> + if (ret) {
> + dev_err(unicam->dev, "stream on failed in subdev\n");
> + goto err_enable_stream;
> + }
> +
> + unicam->subdev.streaming = true;
> +
> + return 0;
> +
> +err_enable_stream:
> + clk_disable_unprepare(unicam->clock);
> +err_vpu_prepare:
> + clk_disable_unprepare(unicam->vpu_clock);
> +err_vpu_clock:
> + if (clk_set_min_rate(unicam->vpu_clock, 0))
> + dev_err(unicam->dev, "failed to reset the VPU clock\n");
> + return ret;
> +}
> +
> +static int unicam_sd_disable_streams(struct v4l2_subdev *sd, u32 pad,
> + u64 streams_mask)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_subdev_state *state;
> + u32 other_pad, other_stream;
> + int ret;
> +
> + if (WARN_ON(streams_mask != 1))
> + return -EINVAL;
> +
> + state = v4l2_subdev_lock_active_state(sd);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, 0,
> + &other_pad, &other_stream);
> +
> + v4l2_subdev_unlock_state(state);
> +
> + if (ret) {
> + dev_err(unicam->dev, "disable streams failed: %d\n", ret);
> + return ret;
> + }
> +
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, disable_streams,
> + unicam->sensor.pad->index, BIT(other_stream));
> +
> + if (ret && ret == -ENOIOCTLCMD) {
> + /* The sensor does not support disable streams. */
> + if (unicam->node[UNICAM_IMAGE_NODE].streaming &&
> + other_pad == unicam->node[UNICAM_METADATA_NODE].pad.index) {
> + /* We won't stop the sensor yet. */
> + unicam->subdev.streaming = false;
> + return 0;
> + }
> + /* There is no more the metadata node, we can stop. */
> + ret = v4l2_subdev_call(unicam->sensor.subdev, video, s_stream, 0);
> + }
> +
> + clk_disable_unprepare(unicam->clock);
> + if (clk_set_min_rate(unicam->vpu_clock, 0))
> + dev_err(unicam->dev, "failed to reset the VPU clock\n");
> + clk_disable_unprepare(unicam->vpu_clock);
> +
> + unicam->subdev.streaming = false;
> +
> + return 0;
> +}
> +
> +static int unicam_subdev_set_pad_format(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_format *format)
> +{
> + struct unicam_device *unicam = sd_to_unicam_device(sd);
> + struct v4l2_mbus_framefmt *sink_format, *source_format;
> + const struct unicam_fmt *fmtinfo;
> + int ret = 0;
> +
> + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && unicam->subdev.streaming)
> + return -EBUSY;
> +
> + /* No transcoding, source and sink formats must match. */
> + if (unicam_sd_pad_is_source(format->pad))
> + return v4l2_subdev_get_fmt(sd, state, format);
> +
> + fmtinfo = unicam_find_format_by_code(format->format.code, format->pad);
> + if (!fmtinfo)
> + fmtinfo = &unicam_image_formats[0];
> +
> + if (format->pad == UNICAM_SD_PAD_SOURCE_IMAGE) {
> + format->format.width = clamp_t(unsigned int,
> + format->format.width,
> + UNICAM_MIN_WIDTH,
> + UNICAM_MAX_WIDTH);
> + format->format.height = clamp_t(unsigned int,
> + format->format.height,
> + UNICAM_MIN_HEIGHT,
> + UNICAM_MAX_HEIGHT);
> + }
> + format->format.field = V4L2_FIELD_NONE;
> +
> + v4l2_subdev_lock_state(state);
> +
> + sink_format = v4l2_state_get_stream_format(state, format->pad,
> + format->stream);
> + source_format = v4l2_subdev_state_get_opposite_stream_format(state,
> + format->pad,
> + format->stream);
> + if (!sink_format || !source_format) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + *sink_format = format->format;
> + *source_format = format->format;
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_subdev_enum_frame_size(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state,
> + struct v4l2_subdev_frame_size_enum *fse)
> +{
> + const struct unicam_fmt *fmtinfo;
> + u32 pad, stream;
> + int ret = -EINVAL;
> +
> + if (fse->index > 0)
> + return ret;
> +
> + v4l2_subdev_lock_state(state);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, fse->pad,
> + fse->stream, &pad,
> + &stream);
> + if (ret)
> + goto out;
> +
> + if (unicam_sd_pad_is_source(fse->pad)) {
> + /* No transcoding, source and sink formats must match. */
> + struct v4l2_mbus_framefmt *fmt;
> +
> + fmt = v4l2_state_get_stream_format(state, pad, stream);
> + if (!fmt)
> + goto out;
> +
> + if (fse->code != fmt->code)
> + goto out;
> +
> + fse->min_width = fmt->width;
> + fse->max_width = fmt->width;
> + fse->min_height = fmt->height;
> + fse->max_height = fmt->height;
> + } else {
> + fmtinfo = unicam_find_format_by_code(fse->code, pad);
> + if (!fmtinfo)
> + goto out;
> +
> + fse->min_width = UNICAM_MIN_WIDTH;
> + fse->max_width = UNICAM_MAX_WIDTH;
> + fse->min_height = UNICAM_MIN_HEIGHT;
> + fse->max_height = UNICAM_MAX_HEIGHT;
> + }
> +
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static const struct v4l2_subdev_video_ops unicam_subdev_video_ops = {
> + .enable_streams = unicam_sd_enable_streams,
> + .disable_streams = unicam_sd_disable_streams,
> +};
> +
> +static const struct v4l2_subdev_pad_ops unicam_subdev_pad_ops = {
> + .init_cfg = unicam_subdev_init_cfg,
> + .enum_mbus_code = unicam_subdev_enum_mbus_code,
> + .get_fmt = v4l2_subdev_get_fmt,
> + .set_fmt = unicam_subdev_set_pad_format,
> + .set_routing = unicam_subdev_set_routing,
> + .enum_frame_size = unicam_subdev_enum_frame_size,
> +};
> +
> +static const struct v4l2_subdev_ops unicam_subdev_ops = {
> + .video = &unicam_subdev_video_ops,
> + .pad = &unicam_subdev_pad_ops,
> +};
> +
> +static const struct media_entity_operations unicam_subdev_media_ops = {
> + .link_validate = v4l2_subdev_link_validate,
> + .has_route = v4l2_subdev_has_route,
> +};
> +
> +static int unicam_init_and_register_subdev(struct unicam_device *unicam)
> +{
> + struct v4l2_subdev *sd = &unicam->subdev.sd;
> + int ret;
> +
> + v4l2_subdev_init(sd, &unicam_subdev_ops);
> + v4l2_set_subdevdata(sd, unicam);
> + unicam->subdev.sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> + unicam->subdev.sd.entity.ops = &unicam_subdev_media_ops;
> + unicam->subdev.sd.dev = unicam->dev;
> + unicam->subdev.sd.owner = THIS_MODULE;
> + unicam->subdev.sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_MULTIPLEXED;
> + snprintf(unicam->subdev.sd.name, sizeof(unicam->subdev.sd.name), "unicam-subdev");
> +
> + unicam->subdev.pads[UNICAM_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
> +
> + unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_IMAGE].flags = MEDIA_PAD_FL_SOURCE;
> + unicam->subdev.pads[UNICAM_SD_PAD_SOURCE_METADATA].flags = MEDIA_PAD_FL_SOURCE;
> +
> + ret = media_entity_pads_init(&sd->entity,
> + ARRAY_SIZE(unicam->subdev.pads), unicam->subdev.pads);
> + if (ret) {
> + dev_err(unicam->dev, "Could not init media entity");
> + return ret;
> + }
> +
> + ret = v4l2_subdev_init_finalize(sd);
> + if (ret) {
> + dev_err(unicam->dev, "Could not finalize init for subdev");
> + return ret;
> + }
> +
> + return v4l2_device_register_subdev(&unicam->v4l2_dev, sd);
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * Videobuf2 Queue Operations
> + */
> +
> +static int unicam_queue_setup(struct vb2_queue *vq,
> + unsigned int *nbuffers,
> + unsigned int *nplanes,
> + unsigned int sizes[],
> + struct device *alloc_devs[])
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vq);
> + struct unicam_device *unicam = node->dev;
> + unsigned int size = is_image_node(node) ?
> + node->v_fmt.fmt.pix.sizeimage :
> + node->v_fmt.fmt.meta.buffersize;
> +
> + if (vq->num_buffers + *nbuffers < 3)
> + *nbuffers = 3 - vq->num_buffers;
> +
> + if (*nplanes) {
> + if (sizes[0] < size) {
> + dev_dbg(unicam->dev, "sizes[0] %i < size %u\n", sizes[0],
> + size);
> + return -EINVAL;
> + }
> + size = sizes[0];
> + }
> +
> + *nplanes = 1;
> + sizes[0] = size;
> +
> + return 0;
> +}
> +
> +static int unicam_buffer_prepare(struct vb2_buffer *vb)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
> + struct unicam_device *unicam = node->dev;
> + struct unicam_buffer *buf = to_unicam_buffer(vb);
> + unsigned long size;
> +
> + if (WARN_ON(!node->fmt))
> + return -EINVAL;
> +
> + size = is_image_node(node) ? node->v_fmt.fmt.pix.sizeimage :
> + node->v_fmt.fmt.meta.buffersize;
> + if (vb2_plane_size(vb, 0) < size) {
> + dev_dbg(unicam->dev, "data will not fit into plane (%lu < %lu)\n",
> + vb2_plane_size(vb, 0), size);
> + return -EINVAL;
> + }
> +
> + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
> + return 0;
> +}
> +
> +static void unicam_buffer_queue(struct vb2_buffer *vb)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
> + struct unicam_buffer *buf = to_unicam_buffer(vb);
> +
> + spin_lock_irq(&node->dma_queue_lock);
> + list_add_tail(&buf->list, &node->dma_queue);
> + spin_unlock_irq(&node->dma_queue_lock);
> +}
> +
> +static void unicam_return_buffers(struct unicam_node *node,
> + enum vb2_buffer_state state)
> +{
> + struct unicam_buffer *buf, *tmp;
> +
> + list_for_each_entry_safe(buf, tmp, &node->dma_queue, list) {
> + list_del(&buf->list);
> + vb2_buffer_done(&buf->vb.vb2_buf, state);
> + }
> +
> + if (node->cur_frm)
> + vb2_buffer_done(&node->cur_frm->vb.vb2_buf,
> + state);
> + if (node->next_frm && node->cur_frm != node->next_frm)
> + vb2_buffer_done(&node->next_frm->vb.vb2_buf,
> + state);
> +
> + node->cur_frm = NULL;
> + node->next_frm = NULL;
> +}
> +
> +static int unicam_video_check_format(struct unicam_node *node)
> +{
> + const struct v4l2_mbus_framefmt *format;
> + struct unicam_device *unicam = node->dev;
> + struct v4l2_subdev_state *state;
> + int ret = 0;
> +
> + state = v4l2_subdev_lock_active_state(&unicam->subdev.sd);
> +
> + format = v4l2_state_get_stream_format(state, UNICAM_SD_PAD_SOURCE_IMAGE, 0);
> + if (!format) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + if (node->fmt->code != format->code ||
> + node->v_fmt.fmt.pix.height != format->height ||
> + node->v_fmt.fmt.pix.width != format->width ||
> + node->v_fmt.fmt.pix.field != format->field) {
> + dev_dbg(unicam->dev, "(%u x %u) %08x %s != (%u x %u) %08x %s\n",
> + node->v_fmt.fmt.pix.width, node->v_fmt.fmt.pix.height,
> + node->fmt->code,
> + v4l2_field_names[node->v_fmt.fmt.pix.field],
> + format->width, format->height, format->code,
> + v4l2_field_names[format->field]);
> + ret = -EPIPE;
> + goto out;
> + }
> +
> +out:
> + v4l2_subdev_unlock_state(state);
> +
> + return ret;
> +}
> +
> +static int unicam_start_streaming(struct vb2_queue *vq, unsigned int count)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vq);
> + struct unicam_device *unicam = node->dev;
> + dma_addr_t buffer_addr[UNICAM_MAX_NODES] = { 0 };
> + struct unicam_buffer *buf;
> + struct v4l2_subdev_state *state;
> + unsigned long flags;
> + unsigned int i;
> + int ret;
> + u32 pad, stream;
> + u32 remote_pad_index = is_image_node(node) ? UNICAM_SD_PAD_SOURCE_IMAGE
> + : UNICAM_SD_PAD_SOURCE_METADATA;
> +
> + /* Look for the route for the given pad and stream. */
> + state = v4l2_subdev_lock_active_state(&unicam->subdev.sd);
> +
> + ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
> + remote_pad_index, 0,
> + &pad, &stream);
> + if (ret)
> + goto err_streaming;
> +
> + v4l2_subdev_unlock_state(state);
> +
> + dev_dbg(unicam->dev, "Starting stream 0 on pad %d on subdev %s\n",
> + remote_pad_index, unicam->subdev.sd.name);
> +
> + /* The metadata node can't be started alone. */
> + if (is_metadata_node(node)) {
> + if (!unicam->node[UNICAM_IMAGE_NODE].streaming) {
> + dev_err(unicam->dev,
> + "Can't start metadata without image\n");
> + return -EINVAL;
> + }
> + dev_dbg(unicam->dev, "starting metadata node\n");
> +
> + spin_lock_irqsave(&node->dma_queue_lock, flags);
> + buf = list_first_entry(&node->dma_queue,
> + struct unicam_buffer, list);
> + dev_dbg(unicam->dev, "buffer %d: %p\n", i, buf);
> + node->cur_frm = buf;
> + node->next_frm = buf;
> + list_del(&buf->list);
> + spin_unlock_irqrestore(&node->dma_queue_lock, flags);
> +
> + buffer_addr[UNICAM_METADATA_NODE] =
> + vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> + dev_dbg(unicam->dev, "buffer %d addr: %lld\n", i, buffer_addr[i]);
> +
> + unicam_start_metadata(unicam, buffer_addr);
> + node->streaming = true;
> + return 0;
> + }
> +
> + ret = pm_runtime_get_sync(unicam->dev);

Note that pm_runtime_get_sync() increments usage_count also in error cases.
I suggest to use pm_runtime_resume_and_get() instead.

> + if (ret < 0) {
> + dev_err(unicam->dev, "pm_runtime_get_sync failed\n");
> + goto err_streaming;
> + }
> +
> + ret = media_pipeline_start(node->video_dev.entity.pads, &unicam->pipe);
> + if (ret < 0) {
> + dev_dbg(unicam->dev, "Failed to start media pipeline: %d\n", ret);
> + goto err_pm_put;
> + }
> +
> + ret = unicam_video_check_format(node);
> + if (ret < 0) {
> + dev_err(unicam->dev, "Video format is incorrect: %d\n", ret);
> + goto error_pipeline;
> + }
> +
> + dev_dbg(unicam->dev, "node %s\n", node->video_dev.name);
> +
> + spin_lock_irqsave(&node->dma_queue_lock, flags);
> + buf = list_first_entry(&node->dma_queue,
> + struct unicam_buffer, list);
> + dev_dbg(unicam->dev, "buffer %d: %p\n", i, buf);
> + node->cur_frm = buf;
> + node->next_frm = buf;
> + list_del(&buf->list);
> + spin_unlock_irqrestore(&node->dma_queue_lock, flags);
> +
> + buffer_addr[UNICAM_IMAGE_NODE] =
> + vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
> +
> + dev_dbg(unicam->dev, "Start unicam\n");
> + unicam_start_rx(unicam, buffer_addr);
> +
> + dev_dbg(unicam->dev, "Enable stream\n");
> + ret = v4l2_subdev_call(&unicam->subdev.sd, video, enable_streams,
> + remote_pad_index, BIT(0));
> + if (ret < 0) {
> + dev_err(unicam->dev, "stream on failed in subdev\n");
> + goto error_pipeline;
> + }
> +
> + node->streaming = true;
> +
> + return 0;
> +
> +error_pipeline:
> + media_pipeline_stop(node->video_dev.entity.pads);
> +err_pm_put:
> + pm_runtime_put_sync(unicam->dev);
> +err_streaming:
> + v4l2_subdev_unlock_state(state);
> + return ret;
> +}
> +
> +static void unicam_stop_streaming(struct vb2_queue *vq)
> +{
> + struct unicam_node *node = vb2_get_drv_priv(vq);
> + struct unicam_device *unicam = node->dev;
> + u32 remote_pad_index = is_image_node(node) ? UNICAM_SD_PAD_SOURCE_IMAGE
> + : UNICAM_SD_PAD_SOURCE_METADATA;
> +
> + node->streaming = false;
> +
> + v4l2_subdev_call(&unicam->subdev.sd, video, disable_streams,
> + remote_pad_index, BIT(0));
> +
> + /* We can stream only with the image node. */
> + if (is_metadata_node(node)) {
> + /*
> + * Allow the hardware to spin in the dummy buffer.
> + * This is only really needed if the embedded data pad is
> + * disabled before the image pad.
> + */
> + unicam_wr_dma_addr(node, node->dummy_buf_dma_addr,
> + UNICAM_DUMMY_BUF_SIZE);
> + goto dequeue_buffers;
> + }
> +
> + unicam_disable(unicam);
> +
> + media_pipeline_stop(node->video_dev.entity.pads);
> + pm_runtime_put(unicam->dev);
> +
> +dequeue_buffers:
> + /* Clear all queued buffers for the node */
> + unicam_return_buffers(node, VB2_BUF_STATE_ERROR);
> +}
> +
> +static const struct vb2_ops unicam_video_qops = {
> + .wait_prepare = vb2_ops_wait_prepare,
> + .wait_finish = vb2_ops_wait_finish,
> + .queue_setup = unicam_queue_setup,
> + .buf_prepare = unicam_buffer_prepare,
> + .buf_queue = unicam_buffer_queue,
> + .start_streaming = unicam_start_streaming,
> + .stop_streaming = unicam_stop_streaming,
> +};
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 video device Operations
> + */
> +
> +static int unicam_querycap(struct file *file, void *priv,
> + struct v4l2_capability *cap)
> +{
> + struct unicam_node *node = video_drvdata(file);
> + struct unicam_device *unicam = node->dev;
> +
> + strscpy(cap->driver, UNICAM_MODULE_NAME, sizeof(cap->driver));
> + strscpy(cap->card, UNICAM_MODULE_NAME, sizeof(cap->card));
> +
> + snprintf(cap->bus_info, sizeof(cap->bus_info),
> + "platform:%s", dev_name(unicam->dev));
> +
> + cap->capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE;
> +
> + return 0;
> +}
> +
> +static int unicam_log_status(struct file *file, void *fh)
> +{
> + struct unicam_node *node = video_drvdata(file);
> + struct unicam_device *unicam = node->dev;
> + u32 reg;
> +
> + /* status for sub devices */
> + v4l2_device_call_all(&unicam->v4l2_dev, 0, core, log_status);
> +
> + dev_info(unicam->dev, "-----Receiver status-----\n");
> + dev_info(unicam->dev, "V4L2 width/height: %ux%u\n",
> + node->v_fmt.fmt.pix.width, node->v_fmt.fmt.pix.height);
> + dev_info(unicam->dev, "Mediabus format: %08x\n", node->fmt->code);
> + dev_info(unicam->dev, "V4L2 format: %08x\n",
> + node->v_fmt.fmt.pix.pixelformat);
> + reg = unicam_reg_read(unicam, UNICAM_IPIPE);
> + dev_info(unicam->dev, "Unpacking/packing: %u / %u\n",
> + unicam_get_field(reg, UNICAM_PUM_MASK),
> + unicam_get_field(reg, UNICAM_PPM_MASK));
> + dev_info(unicam->dev, "----Live data----\n");
> + dev_info(unicam->dev, "Programmed stride: %4u\n",
> + unicam_reg_read(unicam, UNICAM_IBLS));
> + dev_info(unicam->dev, "Detected resolution: %ux%u\n",
> + unicam_reg_read(unicam, UNICAM_IHSTA),
> + unicam_reg_read(unicam, UNICAM_IVSTA));
> + dev_info(unicam->dev, "Write pointer: %08x\n",
> + unicam_reg_read(unicam, UNICAM_IBWP));
> +
> + return 0;
> +}
> +
> +static int unicam_subscribe_event(struct v4l2_fh *fh,
> + const struct v4l2_event_subscription *sub)
> +{
> + switch (sub->type) {
> + case V4L2_EVENT_FRAME_SYNC:
> + return v4l2_event_subscribe(fh, sub, 2, NULL);
> + }
> +
> + return v4l2_ctrl_subscribe_event(fh, sub);
> +}
> +
> +static int unicam_enum_fmt_vid(struct file *file, void *priv,
> + struct v4l2_fmtdesc *f)
> +{
> + unsigned int i, index;
> +
> + for (i = 0, index = 0; i < ARRAY_SIZE(unicam_image_formats); i++) {
> + if (f->mbus_code && unicam_image_formats[i].code != f->mbus_code)
> + continue;
> +
> + if (index == f->index) {
> + f->pixelformat = unicam_image_formats[i].fourcc;
> + return 0;
> + }
> +
> + index++;
> +
> + if (unicam_image_formats[i].unpacked_fourcc) {
> + if (index == f->index) {
> + f->pixelformat = unicam_image_formats[i].unpacked_fourcc;
> + return 0;
> + }
> + index++;
> + }
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int unicam_g_fmt_vid(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> +
> + *f = node->v_fmt;
> +
> + return 0;
> +}
> +
> +static const struct unicam_fmt *
> +unicam_try_fmt(struct unicam_node *node, struct v4l2_format *f)
> +{
> + struct v4l2_pix_format *v4l2_format = &f->fmt.pix;
> + struct unicam_device *unicam = node->dev;
> + const struct unicam_fmt *fmt;
> +
> + /*
> + * Default to the first format if the requested pixel format code isn't
> + * supported.
> + */
> + fmt = unicam_find_format_by_fourcc(v4l2_format->pixelformat,
> + UNICAM_SD_PAD_SOURCE_IMAGE);
> + if (!fmt) {
> + fmt = &unicam_image_formats[0];
> + v4l2_format->pixelformat = fmt->fourcc;
> + }
> +
> + unicam_calc_format_size_bpl(unicam, fmt, f);
> +
> + if (v4l2_format->field == V4L2_FIELD_ANY)
> + v4l2_format->field = V4L2_FIELD_NONE;
> +
> + dev_dbg(unicam->dev, "%s: %08x %ux%u (bytesperline %u sizeimage %u)\n",
> + __func__, v4l2_format->pixelformat,
> + v4l2_format->width, v4l2_format->height,
> + v4l2_format->bytesperline, v4l2_format->sizeimage);
> +
> + return fmt;
> +}
> +
> +static int unicam_try_fmt_vid(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> +
> + unicam_try_fmt(node, f);
> + return 0;
> +}
> +
> +static int unicam_s_fmt_vid(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> + struct unicam_device *unicam = node->dev;
> +
> + if (vb2_is_busy(&node->buffer_queue)) {
> + dev_dbg(unicam->dev, "%s device busy\n", __func__);
> + return -EBUSY;
> + }
> +
> + node->fmt = unicam_try_fmt(node, f);
> +
> + node->v_fmt = *f;
> +
> + return 0;
> +}
> +
> +static int unicam_enum_framesizes(struct file *file, void *fh,
> + struct v4l2_frmsizeenum *fsize)
> +{
> + struct unicam_node *node = video_drvdata(file);
> + int ret = -EINVAL;
> +
> + if (fsize->index > 0)
> + return ret;
> +
> + if (is_image_node(node)) {
> + if (!unicam_find_format_by_fourcc(fsize->pixel_format,
> + UNICAM_SD_PAD_SOURCE_IMAGE))
> + return ret;
> +
> + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> + fsize->stepwise.min_width = UNICAM_MIN_WIDTH;
> + fsize->stepwise.max_width = UNICAM_MAX_WIDTH;
> + fsize->stepwise.step_width = 1;
> + fsize->stepwise.min_height = UNICAM_MIN_HEIGHT;
> + fsize->stepwise.max_height = UNICAM_MAX_HEIGHT;
> + fsize->stepwise.step_height = 1;
> + } else {
> + if (!unicam_find_format_by_fourcc(fsize->pixel_format,
> + UNICAM_SD_PAD_SOURCE_METADATA))
> + return ret;
> +
> + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
> + fsize->discrete.width = UNICAM_EMBEDDED_SIZE;
> + fsize->discrete.height = 1;
> + }
> +
> + return 0;
> +}
> +
> +static int unicam_enum_fmt_meta(struct file *file, void *priv,
> + struct v4l2_fmtdesc *f)
> +{
> + unsigned int i, index;
> +
> + for (i = 0, index = 0; i < ARRAY_SIZE(unicam_meta_formats); i++) {
> + if (f->mbus_code && unicam_meta_formats[i].code != f->mbus_code)
> + continue;
> + if (!unicam_meta_formats[i].metadata_fmt)
> + continue;
> +
> + if (index == f->index) {
> + f->pixelformat = unicam_meta_formats[i].fourcc;
> + f->type = V4L2_BUF_TYPE_META_CAPTURE;
> + return 0;
> + }
> + index++;
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int unicam_g_fmt_meta(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> +
> + f->fmt.meta = node->v_fmt.fmt.meta;
> +
> + return 0;
> +}
> +
> +static int unicam_try_fmt_meta(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + f->fmt.meta.dataformat = V4L2_META_FMT_8;
> + f->fmt.meta.buffersize = UNICAM_EMBEDDED_SIZE;
> +
> + return 0;
> +}
> +
> +static int unicam_s_fmt_meta(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct unicam_node *node = video_drvdata(file);
> +
> + unicam_try_fmt_meta(file, priv, f);
> +
> + node->v_fmt = *f;
> +
> + return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops unicam_ioctl_ops = {
> + .vidioc_querycap = unicam_querycap,
> + .vidioc_enum_fmt_vid_cap = unicam_enum_fmt_vid,
> + .vidioc_g_fmt_vid_cap = unicam_g_fmt_vid,
> + .vidioc_try_fmt_vid_cap = unicam_try_fmt_vid,
> + .vidioc_s_fmt_vid_cap = unicam_s_fmt_vid,
> +
> + .vidioc_enum_fmt_meta_cap = unicam_enum_fmt_meta,
> + .vidioc_g_fmt_meta_cap = unicam_g_fmt_meta,
> + .vidioc_try_fmt_meta_cap = unicam_try_fmt_meta,
> + .vidioc_s_fmt_meta_cap = unicam_s_fmt_meta,
> +
> + .vidioc_enum_framesizes = unicam_enum_framesizes,
> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
> + .vidioc_create_bufs = vb2_ioctl_create_bufs,
> + .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> + .vidioc_querybuf = vb2_ioctl_querybuf,
> + .vidioc_qbuf = vb2_ioctl_qbuf,
> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
> + .vidioc_expbuf = vb2_ioctl_expbuf,
> + .vidioc_streamon = vb2_ioctl_streamon,
> + .vidioc_streamoff = vb2_ioctl_streamoff,
> +
> + .vidioc_log_status = unicam_log_status,
> + .vidioc_subscribe_event = unicam_subscribe_event,
> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +/* unicam capture driver file operations */
> +static const struct v4l2_file_operations unicam_fops = {
> + .owner = THIS_MODULE,
> + .open = v4l2_fh_open,
> + .release = vb2_fop_release,
> + .poll = vb2_fop_poll,
> + .unlocked_ioctl = video_ioctl2,
> + .mmap = vb2_fop_mmap,
> +};
> +
> +static void unicam_set_default_format(struct unicam_node *node)
> +{
> + if (is_image_node(node)) {
> + struct v4l2_pix_format *pix_fmt = &node->v_fmt.fmt.pix;
> +
> + pix_fmt->width = default_image_format.width;
> + pix_fmt->height = default_image_format.height;
> + pix_fmt->field = default_image_format.field;
> + pix_fmt->colorspace = default_image_format.colorspace;
> + pix_fmt->ycbcr_enc = default_image_format.ycbcr_enc;
> + pix_fmt->quantization = default_image_format.quantization;
> + pix_fmt->xfer_func = default_image_format.xfer_func;
> + pix_fmt->pixelformat = unicam_image_formats[0].fourcc;
> + unicam_calc_format_size_bpl(node->dev, &unicam_image_formats[0],
> + &node->v_fmt);
> + node->v_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
> +
> + node->fmt = &unicam_image_formats[0];
> + } else {
> + const struct unicam_fmt *fmt;
> +
> + /* Fix this node format as embedded data. */
> + fmt = &unicam_meta_formats[0];
> + node->fmt = fmt;
> + node->v_fmt.fmt.meta.dataformat = fmt->fourcc;
> + node->v_fmt.fmt.meta.buffersize = UNICAM_EMBEDDED_SIZE;
> + node->v_fmt.type = V4L2_BUF_TYPE_META_CAPTURE;
> + }
> +}
> +
> +static int unicam_register_node(struct unicam_device *unicam,
> + enum unicam_node_type type)
> +{
> + struct video_device *vdev;
> + struct vb2_queue *q;
> + struct unicam_node *node = &unicam->node[type];
> + int ret;
> + u32 pad_index = type == UNICAM_IMAGE_NODE ? UNICAM_SD_PAD_SOURCE_IMAGE
> + : UNICAM_SD_PAD_SOURCE_METADATA;
> +
> + node->dev = unicam_get(unicam);
> + node->id = type;
> +
> + spin_lock_init(&node->dma_queue_lock);
> + mutex_init(&node->lock);
> +
> + INIT_LIST_HEAD(&node->dma_queue);
> +
> + /* Initialize the videobuf2 queue. */
> + q = &node->buffer_queue;
> + q->type = type == UNICAM_IMAGE_NODE ? V4L2_BUF_TYPE_VIDEO_CAPTURE
> + : V4L2_BUF_TYPE_META_CAPTURE;
> + q->io_modes = VB2_MMAP | VB2_DMABUF;
> + q->drv_priv = node;
> + q->ops = &unicam_video_qops;
> + q->mem_ops = &vb2_dma_contig_memops;
> + q->buf_struct_size = sizeof(struct unicam_buffer);
> + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> + q->lock = &node->lock;
> + q->min_buffers_needed = 1;
> + q->dev = unicam->dev;
> +
> + ret = vb2_queue_init(q);
> + if (ret) {
> + dev_err(unicam->dev, "vb2_queue_init() failed\n");
> + goto err_unicam_put;
> + }
> +
> + /* Initialize the video device. */
> + vdev = &node->video_dev;
> + vdev->release = unicam_node_release;
> + vdev->fops = &unicam_fops;
> + vdev->ioctl_ops = &unicam_ioctl_ops;
> + vdev->v4l2_dev = &unicam->v4l2_dev;
> + vdev->vfl_dir = VFL_DIR_RX;
> + vdev->queue = q;
> + vdev->lock = &node->lock;
> + vdev->device_caps = type == UNICAM_IMAGE_NODE ?
> + V4L2_CAP_VIDEO_CAPTURE : V4L2_CAP_META_CAPTURE;
> + vdev->device_caps |= V4L2_CAP_STREAMING | V4L2_CAP_IO_MC;
> +
> + /* Define the device names */
> + snprintf(vdev->name, sizeof(vdev->name), "%s-%s", UNICAM_MODULE_NAME,
> + type == UNICAM_IMAGE_NODE ? "image" : "embedded");
> +
> + video_set_drvdata(vdev, node);
> + if (type == UNICAM_IMAGE_NODE)
> + vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT;
> + node->pad.flags = MEDIA_PAD_FL_SINK;
> + ret = media_entity_pads_init(&vdev->entity, 1, &node->pad);
> + if (ret)
> + goto err_unicam_put;
> +
> + node->dummy_buf_cpu_addr = dma_alloc_coherent(unicam->dev,
> + UNICAM_DUMMY_BUF_SIZE,
> + &node->dummy_buf_dma_addr,
> + GFP_KERNEL);
> + if (!node->dummy_buf_cpu_addr) {
> + dev_err(unicam->dev, "Unable to allocate dummy buffer.\n");
> + ret = -ENOMEM;
> + goto err_entity_cleanup;
> + }
> +
> + unicam_set_default_format(node);
> +
> + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> + if (ret) {
> + dev_err(unicam->dev, "Unable to register video device %s\n",
> + vdev->name);
> + goto err_dma_free;
> + }
> +
> + node->registered = true;
> +
> + ret = media_create_pad_link(&unicam->subdev.sd.entity,
> + pad_index,
> + &node->video_dev.entity,
> + 0,
> + MEDIA_LNK_FL_ENABLED |
> + MEDIA_LNK_FL_IMMUTABLE);
> + if (ret) {
> + /*
> + * No need for cleanup, the caller will unregister the
> + * video device, which will drop the reference on the
> + * device and trigger the cleanup.
> + */
> + dev_err(unicam->dev, "Unable to create pad link for %s",
> + unicam->sensor.subdev->name);
> + return ret;
> + }
> +
> + return 0;
> +
> +err_dma_free:
> + dma_free_coherent(unicam->dev, UNICAM_DUMMY_BUF_SIZE,
> + node->dummy_buf_cpu_addr,
> + node->dummy_buf_dma_addr);
> +err_entity_cleanup:
> + media_entity_cleanup(&vdev->entity);
> +err_unicam_put:
> + unicam_put(unicam);
> + return ret;
> +}
> +
> +static void unicam_unregister_nodes(struct unicam_device *unicam)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
> + struct unicam_node *node = &unicam->node[i];
> +
> + if (node->dummy_buf_cpu_addr)
> + dma_free_coherent(unicam->dev, UNICAM_DUMMY_BUF_SIZE,
> + node->dummy_buf_cpu_addr,
> + node->dummy_buf_dma_addr);
> +
> + if (node->registered) {
> + video_unregister_device(&node->video_dev);
> + node->registered = false;
> + }
> + }
> +}
> +
> +/* -----------------------------------------------------------------------------
> + * V4L2 async notifier
> + */
> +
> +static int
> +unicam_async_bound(struct v4l2_async_notifier *notifier,
> + struct v4l2_subdev *subdev,
> + struct v4l2_async_subdev *asd)
> +{
> + struct unicam_device *unicam = notifier_to_unicam_device(notifier);
> + struct media_pad *sink = &unicam->subdev.sd.entity.pads[UNICAM_SD_PAD_SINK];
> +
> + unicam->sensor.subdev = subdev;
> + dev_dbg(unicam->dev, "Using sensor %s for capture\n",
> + subdev->name);
> +
> + unicam->sensor.pad = sink;
> +
> + return v4l2_create_fwnode_links_to_pad(subdev, sink, MEDIA_LNK_FL_ENABLED |

These would be nicer as wrapped before 80. There are a bunch of others,
too.

> + MEDIA_LNK_FL_IMMUTABLE);
> +}
> +
> +static int unicam_async_complete(struct v4l2_async_notifier *notifier)
> +{
> + struct unicam_device *unicam = notifier_to_unicam_device(notifier);
> + int ret;
> +
> + ret = unicam_register_node(unicam, UNICAM_IMAGE_NODE);
> + if (ret) {
> + dev_err(unicam->dev, "Unable to register image video device.\n");
> + goto unregister;
> + }
> +
> + ret = unicam_register_node(unicam, UNICAM_METADATA_NODE);
> + if (ret) {
> + dev_err(unicam->dev, "Unable to register metadata video device.\n");
> + goto unregister;
> + }
> +
> + ret = v4l2_device_register_subdev_nodes(&unicam->v4l2_dev);
> + if (ret) {
> + dev_err(unicam->dev, "Unable to register subdev nodes.\n");
> + goto unregister;
> + }
> +
> + return 0;
> +
> +unregister:
> + unicam_unregister_nodes(unicam);
> + unicam_put(unicam);
> +
> + return ret;
> +}
> +
> +static const struct v4l2_async_notifier_operations unicam_async_ops = {
> + .bound = unicam_async_bound,
> + .complete = unicam_async_complete,
> +};
> +
> +static int unicam_connect_of_subdevs(struct unicam_device *unicam)
> +{
> + struct v4l2_fwnode_endpoint ep = { };
> + struct fwnode_handle *ep_handle;
> + struct v4l2_async_subdev *asd;
> + unsigned int lane;
> + int ret = -EINVAL;
> +
> + if (of_property_read_u32(unicam->dev->of_node, "brcm,num-data-lanes",
> + &unicam->max_data_lanes) < 0) {

As you're already using fwnode API below, you could use
device_property_read_u32() here.

You can then replace of_device.h by mod_devicetable.h. Up to you.

> + dev_err(unicam->dev, "DT property %s not set\n",
> + "brcm,num-data-lanes");
> + return -EINVAL;
> + }
> +
> + /* Get the local endpoint and remote device. */
> + ep_handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(unicam->dev),
> + 0, 0,
> + FWNODE_GRAPH_ENDPOINT_NEXT);
> + if (!ep_handle) {
> + dev_err(unicam->dev, "No endpoint\n");
> + return -ENODEV;
> + }
> +
> + /* Parse the local endpoint and validate its configuration. */
> + if (v4l2_fwnode_endpoint_alloc_parse(ep_handle, &ep)) {

As you don't need link-frequencies property parsing, you should use
v4l2_fwnode_endpoint_parse(). That avoids having to call
v4l2_fwnode_endpoint_free().

> + dev_err(unicam->dev, "could not parse endpoint\n");
> + goto cleanup_exit;
> + }
> +
> + dev_dbg(unicam->dev, "parsed local endpoint, bus_type %u\n",
> + ep.bus_type);
> +
> + unicam->bus_type = ep.bus_type;
> +
> + switch (ep.bus_type) {
> + case V4L2_MBUS_CSI2_DPHY:
> + switch (ep.bus.mipi_csi2.num_data_lanes) {
> + case 1:
> + case 2:
> + case 4:
> + break;
> +
> + default:
> + dev_err(unicam->dev, "%u data lanes not supported\n",
> + ep.bus.mipi_csi2.num_data_lanes);
> + goto cleanup_exit;
> + }
> +
> + for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
> + if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
> + dev_err(unicam->dev, "data lanes reordering not supported\n");
> + goto cleanup_exit;
> + }
> + }
> +
> + if (ep.bus.mipi_csi2.num_data_lanes > unicam->max_data_lanes) {
> + dev_err(unicam->dev, "endpoint requires %u data lanes when %u are supported\n",
> + ep.bus.mipi_csi2.num_data_lanes,
> + unicam->max_data_lanes);
> + }
> +
> + unicam->active_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
> + unicam->bus_flags = ep.bus.mipi_csi2.flags;
> +
> + break;
> +
> + case V4L2_MBUS_CCP2:
> + if (ep.bus.mipi_csi1.clock_lane != 0 ||
> + ep.bus.mipi_csi1.data_lane != 1) {
> + dev_err(unicam->dev, "unsupported lanes configuration\n");

If the hardware doesn't support lane remapping for CCP2, then that should
be reflected in DT bindings, i.e. data-lanes isn't relevant. There's no
need to check that here.

> + goto cleanup_exit;
> + }
> +
> + unicam->max_data_lanes = 1;
> + unicam->active_data_lanes = 1;
> + unicam->bus_flags = ep.bus.mipi_csi1.strobe;
> + break;
> +
> + default:
> + /* Unsupported bus type */
> + dev_err(unicam->dev, "unsupported bus type %u\n",
> + ep.bus_type);
> + goto cleanup_exit;
> + }
> +
> + dev_dbg(unicam->dev, "%s bus, %u data lanes, flags=0x%08x\n",
> + unicam->bus_type == V4L2_MBUS_CSI2_DPHY ? "CSI-2" : "CCP2",
> + unicam->active_data_lanes, unicam->bus_flags);

V4l2-fwnode already prints this information I believe.

> +
> + /* Initialize and register the async notifier. */
> + v4l2_async_nf_init(&unicam->notifier);
> +
> + asd = v4l2_async_nf_add_fwnode_remote(&unicam->notifier, ep_handle,
> + struct v4l2_async_subdev);
> +
> + fwnode_handle_put(ep_handle);
> + ep_handle = NULL;
> +
> + if (IS_ERR(asd)) {
> + ret = PTR_ERR(asd);
> + goto cleanup_exit;
> + }
> +
> + unicam->notifier.ops = &unicam_async_ops;
> +
> + ret = v4l2_async_nf_register(&unicam->v4l2_dev, &unicam->notifier);
> + if (ret) {
> + dev_err(unicam->dev, "Error registering device notifier: %d\n", ret);
> + goto cleanup_exit;
> + }
> +
> + return 0;
> +
> +cleanup_exit:
> + v4l2_fwnode_endpoint_free(&ep);
> + fwnode_handle_put(ep_handle);
> +
> + return ret;
> +}
> +
> +static int unicam_media_dev_init(struct unicam_device *unicam)
> +{
> + int ret = 0;
> +
> + unicam->mdev.dev = unicam->dev;
> + strscpy(unicam->mdev.model, UNICAM_MODULE_NAME,
> + sizeof(unicam->mdev.model));
> + strscpy(unicam->mdev.serial, "", sizeof(unicam->mdev.serial));
> + snprintf(unicam->mdev.bus_info, sizeof(unicam->mdev.bus_info),
> + "platform:%s", dev_name(unicam->dev));
> + unicam->mdev.hw_revision = 0;
> +
> + media_device_init(&unicam->mdev);
> +
> + unicam->v4l2_dev.mdev = &unicam->mdev;
> +
> + ret = v4l2_device_register(unicam->dev, &unicam->v4l2_dev);
> + if (ret < 0) {
> + dev_err(unicam->dev,
> + "Unable to register v4l2 device\n");
> + return ret;

goto ...;

> + }
> +
> + ret = media_device_register(&unicam->mdev);
> + if (ret < 0) {
> + dev_err(unicam->dev,
> + "Unable to register media-controller device\n");
> + goto err_v4l2_unregister;
> + }
> +
> + return 0;
> +
> +err_v4l2_unregister:
> + v4l2_device_unregister(&unicam->v4l2_dev);

You're missing media_device_cleanup() here, aren't you? And a new label.

> + return ret;
> +}
> +
> +static int unicam_probe(struct platform_device *pdev)
> +{
> + struct unicam_device *unicam;
> + int ret = 0;
> +
> + unicam = kzalloc(sizeof(*unicam), GFP_KERNEL);
> + if (!unicam)
> + return -ENOMEM;
> +
> + kref_init(&unicam->kref);
> + unicam->dev = &pdev->dev;
> + /* set the driver data in platform device */
> + platform_set_drvdata(pdev, unicam);
> +
> + unicam->base = devm_platform_ioremap_resource_byname(pdev, "unicam");
> + if (IS_ERR(unicam->base)) {
> + ret = PTR_ERR(unicam->base);
> + goto err_unicam_put;
> + }
> +
> + unicam->clk_gate_base = devm_platform_ioremap_resource_byname(pdev, "cmi");
> + if (IS_ERR(unicam->clk_gate_base)) {
> + ret = PTR_ERR(unicam->clk_gate_base);
> + goto err_unicam_put;
> + }
> +
> + unicam->clock = devm_clk_get(&pdev->dev, "lp");
> + if (IS_ERR(unicam->clock)) {
> + dev_err(unicam->dev, "Failed to get lp clock\n");
> + ret = PTR_ERR(unicam->clock);
> + goto err_unicam_put;
> + }
> +
> + unicam->vpu_clock = devm_clk_get(&pdev->dev, "vpu");
> + if (IS_ERR(unicam->vpu_clock)) {
> + dev_err(unicam->dev, "Failed to get vpu clock\n");
> + ret = PTR_ERR(unicam->vpu_clock);
> + goto err_unicam_put;
> + }
> +
> + ret = platform_get_irq(pdev, 0);
> + if (ret <= 0) {
> + dev_err(&pdev->dev, "No IRQ resource\n");
> + ret = -EINVAL;
> + goto err_unicam_put;
> + }
> +
> + ret = devm_request_irq(&pdev->dev, ret, unicam_isr, 0,
> + "unicam_capture0", unicam);
> + if (ret) {
> + dev_err(&pdev->dev, "Unable to request interrupt\n");
> + ret = -EINVAL;
> + goto err_unicam_put;
> + }
> +
> + ret = unicam_media_dev_init(unicam);
> + if (ret) {
> + dev_err(unicam->dev,
> + "Unable to initialize media device\n");
> + goto err_unicam_put;
> + }
> +
> + ret = unicam_init_and_register_subdev(unicam);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to register internal subdev\n");
> + goto err_media_unregister;
> + }
> +
> + ret = unicam_connect_of_subdevs(unicam);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to connect subdevs\n");
> + goto err_subdev_unregister;
> + }
> +
> + /* Enable the block power domain */
> + pm_runtime_enable(&pdev->dev);
> +
> + return 0;
> +
> +err_subdev_unregister:
> + v4l2_subdev_cleanup(&unicam->subdev.sd);
> +err_media_unregister:
> + media_device_unregister(&unicam->mdev);
> +err_unicam_put:
> + unicam_put(unicam);
> +
> + return ret;
> +}
> +
> +static int unicam_remove(struct platform_device *pdev)
> +{
> + struct unicam_device *unicam = platform_get_drvdata(pdev);
> +
> + unicam_unregister_nodes(unicam);
> + v4l2_device_unregister(&unicam->v4l2_dev);
> + media_device_unregister(&unicam->mdev);
> + v4l2_async_nf_unregister(&unicam->notifier);
> + unicam_put(unicam);
> +
> + pm_runtime_disable(&pdev->dev);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id unicam_of_match[] = {
> + { .compatible = "brcm,bcm2835-unicam", },
> + { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, unicam_of_match);
> +
> +static struct platform_driver unicam_driver = {
> + .probe = unicam_probe,
> + .remove = unicam_remove,
> + .driver = {
> + .name = UNICAM_MODULE_NAME,
> + .of_match_table = of_match_ptr(unicam_of_match),
> + },
> +};
> +
> +module_platform_driver(unicam_driver);
> +
> +MODULE_AUTHOR("Dave Stevenson <[email protected]>");
> +MODULE_DESCRIPTION("BCM2835 Unicam driver");
> +MODULE_LICENSE("GPL");

--
Kind regards,

Sakari Ailus

2022-02-26 17:50:54

by Stefan Wahren

[permalink] [raw]
Subject: Re: [PATCH v5 00/11] Add support for BCM2835 camera interface (unicam)

Hi Jean-Michel,

Am 20.02.22 um 15:30 schrieb Jean-Michel Hautbois:
> Hi Stefan,
>
> On 16/02/2022 21:57, Stefan Wahren wrote:
>> Hi Jean-Michel,
>>
>> Am 08.02.22 um 16:50 schrieb Jean-Michel Hautbois:
>>> Hello !
>>>
>>> ...
>>>
>>> In order to properly configure the media pipeline, it is needed to call
>>> the usual ioctls, and configure routing in order to send the embedded
>>> data from the sensor to the "unicam-embedded" device node :
>>>
>>> ```
>>> media=0
>>> media-ctl -d${media} -l "'imx219 2-0010':0->'unicam-subdev':0 [1]"
>>> media-ctl -d${media} -l "'unicam-subdev':1->'unicam-image':0 [1]"
>>> media-ctl -d${media} -v -R "'unicam-subdev' [0/0->1/0[1],0/1->2/0[1]]"
>>> media-ctl -d${media} -V "'imx219 2-0010':0/0
>>> [fmt:SRGGB10_1X10/3280x2464 field:none]"
>>> v4l2-ctl -d0 --set-fmt-video
>>> width=3280,height=2464,pixelformat='pRAA',field=none
>>> media-ctl -d${media} -v -V "'imx219 2-0010':0/1
>>> [fmt:METADATA_8/16384x1 field:none]"
>>> media-ctl -d${media} -p
>>> ```
>>
>> i tried to test the unicam driver on a Raspberry Pi 4 with the imx219
>> camera (based on 5.17-rc4). The unicam & imx219 driver probes and
>> /dev/video0 is created.
>>
>> If a execute the first media-ctl command, it complains with invalid
>> argument 22. Is there a more fool-proof variant to configure this (a
>> script or something else)? I never used the unicam driver before.
>>
>> Here is the output of
>>
>> $ mediactl -d0 -p
>>
>
> Based on your output, I suppose the issue is the naming of the imx219
> media entity ('imx219 2-0010' vs 'imx219 5-0010').
> You could add a '-v' in the line to help you I suppose.
>
> A more bullet-proof version of the commands is certainly doable, not
> sure how though as I would not like to rewrite a libcamera-like
> command ;-).

Today i had a little bit time and manage to get the commands working.

After running the yavta command, the kernel put the following:

[  405.908674] cma: cma_alloc: linux,cma: alloc failed, req-size: 2484
pages, ret: -12
[  405.908695] unicam fe801000.csi: dma alloc of size 10174464 failed

As per default the CMA size with mainline DTB is 64 MB. After that i
increase the value to 256 MB and the issue disappeared. Maybe this is
something which could be integrated into the overlay. But at least a
note about this in the cover letter of the next version of this series
would be nice.

Best regards


2023-07-02 16:05:29

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Hi Sakari,

On Fri, Feb 25, 2022 at 11:29:18AM +0200, Sakari Ailus wrote:
> On Tue, Feb 08, 2022 at 04:50:20PM +0100, Jean-Michel Hautbois wrote:
> > Add driver for the Unicam camera receiver block on BCM283x processors.
> > It is represented as two video device nodes: unicam-image and
> > unicam-embedded which are connected to an internal subdev (named
> > unicam-subdev) in order to manage streams routing.
> >
> > Signed-off-by: Dave Stevenson <[email protected]>
> > Signed-off-by: Naushir Patuck <[email protected]>
> > Signed-off-by: Jean-Michel Hautbois <[email protected]>
> >
> > ---
> > v4:
> > - Add the vendor prefox for DT name
> > - Use the reg-names in DT parsing
> > - Remove MAINTAINERS entry
> >
> > v3 main changes:
> > - Change code organization
> > - Remove unused variables
> > - Correct the fmt_meta functions
> > - Rewrite the start/stop streaming
> > - You can now start the image node alone, but not the metadata one
> > - The buffers are allocated per-node
> > - only the required stream is started, if the route exists and is
> > enabled
> > - Prefix the macros with UNICAM_ to not have too generic names
> > - Drop colorspace support
> > -> This is causing issues in the try-fmt v4l2-compliance test
> > test VIDIOC_G_FMT: OK
> > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > test VIDIOC_TRY_FMT: FAIL
> > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > test VIDIOC_S_FMT: FAIL
> >
> > v2: Remove the unicam_{info,debug,error} macros and use
> > dev_dbg/dev_err instead.
> > ---
> > drivers/media/platform/Kconfig | 1 +
> > drivers/media/platform/Makefile | 2 +
> > drivers/media/platform/bcm2835/Kconfig | 21 +
> > drivers/media/platform/bcm2835/Makefile | 3 +
> > .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> > .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> > 6 files changed, 2850 insertions(+)
> > create mode 100644 drivers/media/platform/bcm2835/Kconfig
> > create mode 100644 drivers/media/platform/bcm2835/Makefile
> > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c

[snip]

> > diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > new file mode 100644
> > index 000000000000..b8d297076a02
> > --- /dev/null
> > +++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h

[snip]

> > +static int unicam_connect_of_subdevs(struct unicam_device *unicam)
> > +{
> > + struct v4l2_fwnode_endpoint ep = { };
> > + struct fwnode_handle *ep_handle;
> > + struct v4l2_async_subdev *asd;
> > + unsigned int lane;
> > + int ret = -EINVAL;
> > +
> > + if (of_property_read_u32(unicam->dev->of_node, "brcm,num-data-lanes",
> > + &unicam->max_data_lanes) < 0) {
>
> As you're already using fwnode API below, you could use
> device_property_read_u32() here.
>
> You can then replace of_device.h by mod_devicetable.h. Up to you.
>
> > + dev_err(unicam->dev, "DT property %s not set\n",
> > + "brcm,num-data-lanes");
> > + return -EINVAL;
> > + }
> > +
> > + /* Get the local endpoint and remote device. */
> > + ep_handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(unicam->dev),
> > + 0, 0,
> > + FWNODE_GRAPH_ENDPOINT_NEXT);
> > + if (!ep_handle) {
> > + dev_err(unicam->dev, "No endpoint\n");
> > + return -ENODEV;
> > + }
> > +
> > + /* Parse the local endpoint and validate its configuration. */
> > + if (v4l2_fwnode_endpoint_alloc_parse(ep_handle, &ep)) {
>
> As you don't need link-frequencies property parsing, you should use
> v4l2_fwnode_endpoint_parse(). That avoids having to call
> v4l2_fwnode_endpoint_free().
>
> > + dev_err(unicam->dev, "could not parse endpoint\n");
> > + goto cleanup_exit;
> > + }
> > +
> > + dev_dbg(unicam->dev, "parsed local endpoint, bus_type %u\n",
> > + ep.bus_type);
> > +
> > + unicam->bus_type = ep.bus_type;
> > +
> > + switch (ep.bus_type) {
> > + case V4L2_MBUS_CSI2_DPHY:
> > + switch (ep.bus.mipi_csi2.num_data_lanes) {
> > + case 1:
> > + case 2:
> > + case 4:
> > + break;
> > +
> > + default:
> > + dev_err(unicam->dev, "%u data lanes not supported\n",
> > + ep.bus.mipi_csi2.num_data_lanes);
> > + goto cleanup_exit;
> > + }
> > +
> > + for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
> > + if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
> > + dev_err(unicam->dev, "data lanes reordering not supported\n");
> > + goto cleanup_exit;
> > + }
> > + }
> > +
> > + if (ep.bus.mipi_csi2.num_data_lanes > unicam->max_data_lanes) {
> > + dev_err(unicam->dev, "endpoint requires %u data lanes when %u are supported\n",
> > + ep.bus.mipi_csi2.num_data_lanes,
> > + unicam->max_data_lanes);
> > + }
> > +
> > + unicam->active_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
> > + unicam->bus_flags = ep.bus.mipi_csi2.flags;
> > +
> > + break;
> > +
> > + case V4L2_MBUS_CCP2:
> > + if (ep.bus.mipi_csi1.clock_lane != 0 ||
> > + ep.bus.mipi_csi1.data_lane != 1) {
> > + dev_err(unicam->dev, "unsupported lanes configuration\n");
>
> If the hardware doesn't support lane remapping for CCP2, then that should
> be reflected in DT bindings, i.e. data-lanes isn't relevant. There's no
> need to check that here.

Should the above check for CSI-2 be dropped as well then ?

> > + goto cleanup_exit;
> > + }
> > +
> > + unicam->max_data_lanes = 1;
> > + unicam->active_data_lanes = 1;
> > + unicam->bus_flags = ep.bus.mipi_csi1.strobe;
> > + break;
> > +
> > + default:
> > + /* Unsupported bus type */
> > + dev_err(unicam->dev, "unsupported bus type %u\n",
> > + ep.bus_type);
> > + goto cleanup_exit;
> > + }
> > +
> > + dev_dbg(unicam->dev, "%s bus, %u data lanes, flags=0x%08x\n",
> > + unicam->bus_type == V4L2_MBUS_CSI2_DPHY ? "CSI-2" : "CCP2",
> > + unicam->active_data_lanes, unicam->bus_flags);
>
> V4l2-fwnode already prints this information I believe.

True. It does so with pr_debug() though, it would be nice to use
dev_dbg(). That's a candidate for a separate fix of course.

> > +
> > + /* Initialize and register the async notifier. */
> > + v4l2_async_nf_init(&unicam->notifier);
> > +
> > + asd = v4l2_async_nf_add_fwnode_remote(&unicam->notifier, ep_handle,
> > + struct v4l2_async_subdev);
> > +
> > + fwnode_handle_put(ep_handle);
> > + ep_handle = NULL;
> > +
> > + if (IS_ERR(asd)) {
> > + ret = PTR_ERR(asd);
> > + goto cleanup_exit;
> > + }
> > +
> > + unicam->notifier.ops = &unicam_async_ops;
> > +
> > + ret = v4l2_async_nf_register(&unicam->v4l2_dev, &unicam->notifier);
> > + if (ret) {
> > + dev_err(unicam->dev, "Error registering device notifier: %d\n", ret);
> > + goto cleanup_exit;
> > + }
> > +
> > + return 0;
> > +
> > +cleanup_exit:
> > + v4l2_fwnode_endpoint_free(&ep);
> > + fwnode_handle_put(ep_handle);
> > +
> > + return ret;
> > +}

[snip]

--
Regards,

Laurent Pinchart

2023-07-02 18:45:40

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Hi Laurent,

On Sun, Jul 02, 2023 at 06:23:56PM +0300, Laurent Pinchart wrote:
> Hi Sakari,
>
> On Fri, Feb 25, 2022 at 11:29:18AM +0200, Sakari Ailus wrote:
> > On Tue, Feb 08, 2022 at 04:50:20PM +0100, Jean-Michel Hautbois wrote:
> > > Add driver for the Unicam camera receiver block on BCM283x processors.
> > > It is represented as two video device nodes: unicam-image and
> > > unicam-embedded which are connected to an internal subdev (named
> > > unicam-subdev) in order to manage streams routing.
> > >
> > > Signed-off-by: Dave Stevenson <[email protected]>
> > > Signed-off-by: Naushir Patuck <[email protected]>
> > > Signed-off-by: Jean-Michel Hautbois <[email protected]>
> > >
> > > ---
> > > v4:
> > > - Add the vendor prefox for DT name
> > > - Use the reg-names in DT parsing
> > > - Remove MAINTAINERS entry
> > >
> > > v3 main changes:
> > > - Change code organization
> > > - Remove unused variables
> > > - Correct the fmt_meta functions
> > > - Rewrite the start/stop streaming
> > > - You can now start the image node alone, but not the metadata one
> > > - The buffers are allocated per-node
> > > - only the required stream is started, if the route exists and is
> > > enabled
> > > - Prefix the macros with UNICAM_ to not have too generic names
> > > - Drop colorspace support
> > > -> This is causing issues in the try-fmt v4l2-compliance test
> > > test VIDIOC_G_FMT: OK
> > > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > > test VIDIOC_TRY_FMT: FAIL
> > > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > > test VIDIOC_S_FMT: FAIL
> > >
> > > v2: Remove the unicam_{info,debug,error} macros and use
> > > dev_dbg/dev_err instead.
> > > ---
> > > drivers/media/platform/Kconfig | 1 +
> > > drivers/media/platform/Makefile | 2 +
> > > drivers/media/platform/bcm2835/Kconfig | 21 +
> > > drivers/media/platform/bcm2835/Makefile | 3 +
> > > .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> > > .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> > > 6 files changed, 2850 insertions(+)
> > > create mode 100644 drivers/media/platform/bcm2835/Kconfig
> > > create mode 100644 drivers/media/platform/bcm2835/Makefile
> > > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c
>
> [snip]
>
> > > diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > new file mode 100644
> > > index 000000000000..b8d297076a02
> > > --- /dev/null
> > > +++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
>
> [snip]
>
> > > +static int unicam_connect_of_subdevs(struct unicam_device *unicam)
> > > +{
> > > + struct v4l2_fwnode_endpoint ep = { };
> > > + struct fwnode_handle *ep_handle;
> > > + struct v4l2_async_subdev *asd;
> > > + unsigned int lane;
> > > + int ret = -EINVAL;
> > > +
> > > + if (of_property_read_u32(unicam->dev->of_node, "brcm,num-data-lanes",
> > > + &unicam->max_data_lanes) < 0) {
> >
> > As you're already using fwnode API below, you could use
> > device_property_read_u32() here.
> >
> > You can then replace of_device.h by mod_devicetable.h. Up to you.
> >
> > > + dev_err(unicam->dev, "DT property %s not set\n",
> > > + "brcm,num-data-lanes");
> > > + return -EINVAL;
> > > + }
> > > +
> > > + /* Get the local endpoint and remote device. */
> > > + ep_handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(unicam->dev),
> > > + 0, 0,
> > > + FWNODE_GRAPH_ENDPOINT_NEXT);
> > > + if (!ep_handle) {
> > > + dev_err(unicam->dev, "No endpoint\n");
> > > + return -ENODEV;
> > > + }
> > > +
> > > + /* Parse the local endpoint and validate its configuration. */
> > > + if (v4l2_fwnode_endpoint_alloc_parse(ep_handle, &ep)) {
> >
> > As you don't need link-frequencies property parsing, you should use
> > v4l2_fwnode_endpoint_parse(). That avoids having to call
> > v4l2_fwnode_endpoint_free().
> >
> > > + dev_err(unicam->dev, "could not parse endpoint\n");
> > > + goto cleanup_exit;
> > > + }
> > > +
> > > + dev_dbg(unicam->dev, "parsed local endpoint, bus_type %u\n",
> > > + ep.bus_type);
> > > +
> > > + unicam->bus_type = ep.bus_type;
> > > +
> > > + switch (ep.bus_type) {
> > > + case V4L2_MBUS_CSI2_DPHY:
> > > + switch (ep.bus.mipi_csi2.num_data_lanes) {
> > > + case 1:
> > > + case 2:
> > > + case 4:
> > > + break;
> > > +
> > > + default:
> > > + dev_err(unicam->dev, "%u data lanes not supported\n",
> > > + ep.bus.mipi_csi2.num_data_lanes);
> > > + goto cleanup_exit;
> > > + }
> > > +
> > > + for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
> > > + if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
> > > + dev_err(unicam->dev, "data lanes reordering not supported\n");
> > > + goto cleanup_exit;
> > > + }
> > > + }
> > > +
> > > + if (ep.bus.mipi_csi2.num_data_lanes > unicam->max_data_lanes) {
> > > + dev_err(unicam->dev, "endpoint requires %u data lanes when %u are supported\n",
> > > + ep.bus.mipi_csi2.num_data_lanes,
> > > + unicam->max_data_lanes);
> > > + }
> > > +
> > > + unicam->active_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
> > > + unicam->bus_flags = ep.bus.mipi_csi2.flags;
> > > +
> > > + break;
> > > +
> > > + case V4L2_MBUS_CCP2:
> > > + if (ep.bus.mipi_csi1.clock_lane != 0 ||
> > > + ep.bus.mipi_csi1.data_lane != 1) {
> > > + dev_err(unicam->dev, "unsupported lanes configuration\n");
> >
> > If the hardware doesn't support lane remapping for CCP2, then that should
> > be reflected in DT bindings, i.e. data-lanes isn't relevant. There's no
> > need to check that here.
>
> Should the above check for CSI-2 be dropped as well then ?

Same for CSI-2, too: if there's nothing to configure there (lane remapping)
there's no need to validate that part of the DT either.

>
> > > + goto cleanup_exit;
> > > + }
> > > +
> > > + unicam->max_data_lanes = 1;
> > > + unicam->active_data_lanes = 1;
> > > + unicam->bus_flags = ep.bus.mipi_csi1.strobe;
> > > + break;
> > > +
> > > + default:
> > > + /* Unsupported bus type */
> > > + dev_err(unicam->dev, "unsupported bus type %u\n",
> > > + ep.bus_type);
> > > + goto cleanup_exit;
> > > + }
> > > +
> > > + dev_dbg(unicam->dev, "%s bus, %u data lanes, flags=0x%08x\n",
> > > + unicam->bus_type == V4L2_MBUS_CSI2_DPHY ? "CSI-2" : "CCP2",
> > > + unicam->active_data_lanes, unicam->bus_flags);
> >
> > V4l2-fwnode already prints this information I believe.
>
> True. It does so with pr_debug() though, it would be nice to use
> dev_dbg(). That's a candidate for a separate fix of course.

The reason for using pr_debug() is that the device isn't used by the fwnode
framework. Would you add that just for debug prints? Note that the device
nodes themselves are already being printed so it adds little to the
usefulness of the messages.

--
Kind regards,

Sakari Ailus

2023-07-02 21:58:09

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Hi Sakari,

On Mon, Jul 03, 2023 at 12:45:07AM +0300, Laurent Pinchart wrote:
> On Sun, Jul 02, 2023 at 06:18:21PM +0000, Sakari Ailus wrote:
> > On Sun, Jul 02, 2023 at 06:23:56PM +0300, Laurent Pinchart wrote:
> > > On Fri, Feb 25, 2022 at 11:29:18AM +0200, Sakari Ailus wrote:
> > > > On Tue, Feb 08, 2022 at 04:50:20PM +0100, Jean-Michel Hautbois wrote:
> > > > > Add driver for the Unicam camera receiver block on BCM283x processors.
> > > > > It is represented as two video device nodes: unicam-image and
> > > > > unicam-embedded which are connected to an internal subdev (named
> > > > > unicam-subdev) in order to manage streams routing.
> > > > >
> > > > > Signed-off-by: Dave Stevenson <[email protected]>
> > > > > Signed-off-by: Naushir Patuck <[email protected]>
> > > > > Signed-off-by: Jean-Michel Hautbois <[email protected]>
> > > > >
> > > > > ---
> > > > > v4:
> > > > > - Add the vendor prefox for DT name
> > > > > - Use the reg-names in DT parsing
> > > > > - Remove MAINTAINERS entry
> > > > >
> > > > > v3 main changes:
> > > > > - Change code organization
> > > > > - Remove unused variables
> > > > > - Correct the fmt_meta functions
> > > > > - Rewrite the start/stop streaming
> > > > > - You can now start the image node alone, but not the metadata one
> > > > > - The buffers are allocated per-node
> > > > > - only the required stream is started, if the route exists and is
> > > > > enabled
> > > > > - Prefix the macros with UNICAM_ to not have too generic names
> > > > > - Drop colorspace support
> > > > > -> This is causing issues in the try-fmt v4l2-compliance test
> > > > > test VIDIOC_G_FMT: OK
> > > > > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > > > > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > > > > test VIDIOC_TRY_FMT: FAIL
> > > > > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > > > > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > > > > test VIDIOC_S_FMT: FAIL
> > > > >
> > > > > v2: Remove the unicam_{info,debug,error} macros and use
> > > > > dev_dbg/dev_err instead.
> > > > > ---
> > > > > drivers/media/platform/Kconfig | 1 +
> > > > > drivers/media/platform/Makefile | 2 +
> > > > > drivers/media/platform/bcm2835/Kconfig | 21 +
> > > > > drivers/media/platform/bcm2835/Makefile | 3 +
> > > > > .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> > > > > .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> > > > > 6 files changed, 2850 insertions(+)
> > > > > create mode 100644 drivers/media/platform/bcm2835/Kconfig
> > > > > create mode 100644 drivers/media/platform/bcm2835/Makefile
> > > > > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > > > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c
> > >
> > > [snip]
> > >
> > > > > diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > > > new file mode 100644
> > > > > index 000000000000..b8d297076a02
> > > > > --- /dev/null
> > > > > +++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > >
> > > [snip]
> > >
> > > > > +static int unicam_connect_of_subdevs(struct unicam_device *unicam)
> > > > > +{
> > > > > + struct v4l2_fwnode_endpoint ep = { };
> > > > > + struct fwnode_handle *ep_handle;
> > > > > + struct v4l2_async_subdev *asd;
> > > > > + unsigned int lane;
> > > > > + int ret = -EINVAL;
> > > > > +
> > > > > + if (of_property_read_u32(unicam->dev->of_node, "brcm,num-data-lanes",
> > > > > + &unicam->max_data_lanes) < 0) {
> > > >
> > > > As you're already using fwnode API below, you could use
> > > > device_property_read_u32() here.
> > > >
> > > > You can then replace of_device.h by mod_devicetable.h. Up to you.
> > > >
> > > > > + dev_err(unicam->dev, "DT property %s not set\n",
> > > > > + "brcm,num-data-lanes");
> > > > > + return -EINVAL;
> > > > > + }
> > > > > +
> > > > > + /* Get the local endpoint and remote device. */
> > > > > + ep_handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(unicam->dev),
> > > > > + 0, 0,
> > > > > + FWNODE_GRAPH_ENDPOINT_NEXT);
> > > > > + if (!ep_handle) {
> > > > > + dev_err(unicam->dev, "No endpoint\n");
> > > > > + return -ENODEV;
> > > > > + }
> > > > > +
> > > > > + /* Parse the local endpoint and validate its configuration. */
> > > > > + if (v4l2_fwnode_endpoint_alloc_parse(ep_handle, &ep)) {
> > > >
> > > > As you don't need link-frequencies property parsing, you should use
> > > > v4l2_fwnode_endpoint_parse(). That avoids having to call
> > > > v4l2_fwnode_endpoint_free().
> > > >
> > > > > + dev_err(unicam->dev, "could not parse endpoint\n");
> > > > > + goto cleanup_exit;
> > > > > + }
> > > > > +
> > > > > + dev_dbg(unicam->dev, "parsed local endpoint, bus_type %u\n",
> > > > > + ep.bus_type);
> > > > > +
> > > > > + unicam->bus_type = ep.bus_type;
> > > > > +
> > > > > + switch (ep.bus_type) {
> > > > > + case V4L2_MBUS_CSI2_DPHY:
> > > > > + switch (ep.bus.mipi_csi2.num_data_lanes) {
> > > > > + case 1:
> > > > > + case 2:
> > > > > + case 4:
> > > > > + break;
> > > > > +
> > > > > + default:
> > > > > + dev_err(unicam->dev, "%u data lanes not supported\n",
> > > > > + ep.bus.mipi_csi2.num_data_lanes);
> > > > > + goto cleanup_exit;
> > > > > + }
> > > > > +
> > > > > + for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
> > > > > + if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
> > > > > + dev_err(unicam->dev, "data lanes reordering not supported\n");
> > > > > + goto cleanup_exit;
> > > > > + }
> > > > > + }
> > > > > +
> > > > > + if (ep.bus.mipi_csi2.num_data_lanes > unicam->max_data_lanes) {
> > > > > + dev_err(unicam->dev, "endpoint requires %u data lanes when %u are supported\n",
> > > > > + ep.bus.mipi_csi2.num_data_lanes,
> > > > > + unicam->max_data_lanes);
> > > > > + }
> > > > > +
> > > > > + unicam->active_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
> > > > > + unicam->bus_flags = ep.bus.mipi_csi2.flags;
> > > > > +
> > > > > + break;
> > > > > +
> > > > > + case V4L2_MBUS_CCP2:
> > > > > + if (ep.bus.mipi_csi1.clock_lane != 0 ||
> > > > > + ep.bus.mipi_csi1.data_lane != 1) {
> > > > > + dev_err(unicam->dev, "unsupported lanes configuration\n");
> > > >
> > > > If the hardware doesn't support lane remapping for CCP2, then that should
> > > > be reflected in DT bindings, i.e. data-lanes isn't relevant. There's no
> > > > need to check that here.
> > >
> > > Should the above check for CSI-2 be dropped as well then ?
> >
> > Same for CSI-2, too: if there's nothing to configure there (lane remapping)
> > there's no need to validate that part of the DT either.
>
> OK, I'll drop that.

Actually, I'm wondering if it would make sense to tell the parsing
functions whether lane reordering is supported or not. The checks could
then be moved to the framework. What do you think ?

> > > > > + goto cleanup_exit;
> > > > > + }
> > > > > +
> > > > > + unicam->max_data_lanes = 1;
> > > > > + unicam->active_data_lanes = 1;
> > > > > + unicam->bus_flags = ep.bus.mipi_csi1.strobe;
> > > > > + break;
> > > > > +
> > > > > + default:
> > > > > + /* Unsupported bus type */
> > > > > + dev_err(unicam->dev, "unsupported bus type %u\n",
> > > > > + ep.bus_type);
> > > > > + goto cleanup_exit;
> > > > > + }
> > > > > +
> > > > > + dev_dbg(unicam->dev, "%s bus, %u data lanes, flags=0x%08x\n",
> > > > > + unicam->bus_type == V4L2_MBUS_CSI2_DPHY ? "CSI-2" : "CCP2",
> > > > > + unicam->active_data_lanes, unicam->bus_flags);
> > > >
> > > > V4l2-fwnode already prints this information I believe.
> > >
> > > True. It does so with pr_debug() though, it would be nice to use
> > > dev_dbg(). That's a candidate for a separate fix of course.
> >
> > The reason for using pr_debug() is that the device isn't used by the fwnode
> > framework. Would you add that just for debug prints? Note that the device
> > nodes themselves are already being printed so it adds little to the
> > usefulness of the messages.
>
> I'll send patches, we can discuss their usefulness there.

--
Regards,

Laurent Pinchart

2023-07-02 22:03:17

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Hi Sakari,

On Sun, Jul 02, 2023 at 06:18:21PM +0000, Sakari Ailus wrote:
> On Sun, Jul 02, 2023 at 06:23:56PM +0300, Laurent Pinchart wrote:
> > On Fri, Feb 25, 2022 at 11:29:18AM +0200, Sakari Ailus wrote:
> > > On Tue, Feb 08, 2022 at 04:50:20PM +0100, Jean-Michel Hautbois wrote:
> > > > Add driver for the Unicam camera receiver block on BCM283x processors.
> > > > It is represented as two video device nodes: unicam-image and
> > > > unicam-embedded which are connected to an internal subdev (named
> > > > unicam-subdev) in order to manage streams routing.
> > > >
> > > > Signed-off-by: Dave Stevenson <[email protected]>
> > > > Signed-off-by: Naushir Patuck <[email protected]>
> > > > Signed-off-by: Jean-Michel Hautbois <[email protected]>
> > > >
> > > > ---
> > > > v4:
> > > > - Add the vendor prefox for DT name
> > > > - Use the reg-names in DT parsing
> > > > - Remove MAINTAINERS entry
> > > >
> > > > v3 main changes:
> > > > - Change code organization
> > > > - Remove unused variables
> > > > - Correct the fmt_meta functions
> > > > - Rewrite the start/stop streaming
> > > > - You can now start the image node alone, but not the metadata one
> > > > - The buffers are allocated per-node
> > > > - only the required stream is started, if the route exists and is
> > > > enabled
> > > > - Prefix the macros with UNICAM_ to not have too generic names
> > > > - Drop colorspace support
> > > > -> This is causing issues in the try-fmt v4l2-compliance test
> > > > test VIDIOC_G_FMT: OK
> > > > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > > > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > > > test VIDIOC_TRY_FMT: FAIL
> > > > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > > > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > > > test VIDIOC_S_FMT: FAIL
> > > >
> > > > v2: Remove the unicam_{info,debug,error} macros and use
> > > > dev_dbg/dev_err instead.
> > > > ---
> > > > drivers/media/platform/Kconfig | 1 +
> > > > drivers/media/platform/Makefile | 2 +
> > > > drivers/media/platform/bcm2835/Kconfig | 21 +
> > > > drivers/media/platform/bcm2835/Makefile | 3 +
> > > > .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> > > > .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> > > > 6 files changed, 2850 insertions(+)
> > > > create mode 100644 drivers/media/platform/bcm2835/Kconfig
> > > > create mode 100644 drivers/media/platform/bcm2835/Makefile
> > > > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c
> >
> > [snip]
> >
> > > > diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > > new file mode 100644
> > > > index 000000000000..b8d297076a02
> > > > --- /dev/null
> > > > +++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> >
> > [snip]
> >
> > > > +static int unicam_connect_of_subdevs(struct unicam_device *unicam)
> > > > +{
> > > > + struct v4l2_fwnode_endpoint ep = { };
> > > > + struct fwnode_handle *ep_handle;
> > > > + struct v4l2_async_subdev *asd;
> > > > + unsigned int lane;
> > > > + int ret = -EINVAL;
> > > > +
> > > > + if (of_property_read_u32(unicam->dev->of_node, "brcm,num-data-lanes",
> > > > + &unicam->max_data_lanes) < 0) {
> > >
> > > As you're already using fwnode API below, you could use
> > > device_property_read_u32() here.
> > >
> > > You can then replace of_device.h by mod_devicetable.h. Up to you.
> > >
> > > > + dev_err(unicam->dev, "DT property %s not set\n",
> > > > + "brcm,num-data-lanes");
> > > > + return -EINVAL;
> > > > + }
> > > > +
> > > > + /* Get the local endpoint and remote device. */
> > > > + ep_handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(unicam->dev),
> > > > + 0, 0,
> > > > + FWNODE_GRAPH_ENDPOINT_NEXT);
> > > > + if (!ep_handle) {
> > > > + dev_err(unicam->dev, "No endpoint\n");
> > > > + return -ENODEV;
> > > > + }
> > > > +
> > > > + /* Parse the local endpoint and validate its configuration. */
> > > > + if (v4l2_fwnode_endpoint_alloc_parse(ep_handle, &ep)) {
> > >
> > > As you don't need link-frequencies property parsing, you should use
> > > v4l2_fwnode_endpoint_parse(). That avoids having to call
> > > v4l2_fwnode_endpoint_free().
> > >
> > > > + dev_err(unicam->dev, "could not parse endpoint\n");
> > > > + goto cleanup_exit;
> > > > + }
> > > > +
> > > > + dev_dbg(unicam->dev, "parsed local endpoint, bus_type %u\n",
> > > > + ep.bus_type);
> > > > +
> > > > + unicam->bus_type = ep.bus_type;
> > > > +
> > > > + switch (ep.bus_type) {
> > > > + case V4L2_MBUS_CSI2_DPHY:
> > > > + switch (ep.bus.mipi_csi2.num_data_lanes) {
> > > > + case 1:
> > > > + case 2:
> > > > + case 4:
> > > > + break;
> > > > +
> > > > + default:
> > > > + dev_err(unicam->dev, "%u data lanes not supported\n",
> > > > + ep.bus.mipi_csi2.num_data_lanes);
> > > > + goto cleanup_exit;
> > > > + }
> > > > +
> > > > + for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
> > > > + if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
> > > > + dev_err(unicam->dev, "data lanes reordering not supported\n");
> > > > + goto cleanup_exit;
> > > > + }
> > > > + }
> > > > +
> > > > + if (ep.bus.mipi_csi2.num_data_lanes > unicam->max_data_lanes) {
> > > > + dev_err(unicam->dev, "endpoint requires %u data lanes when %u are supported\n",
> > > > + ep.bus.mipi_csi2.num_data_lanes,
> > > > + unicam->max_data_lanes);
> > > > + }
> > > > +
> > > > + unicam->active_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
> > > > + unicam->bus_flags = ep.bus.mipi_csi2.flags;
> > > > +
> > > > + break;
> > > > +
> > > > + case V4L2_MBUS_CCP2:
> > > > + if (ep.bus.mipi_csi1.clock_lane != 0 ||
> > > > + ep.bus.mipi_csi1.data_lane != 1) {
> > > > + dev_err(unicam->dev, "unsupported lanes configuration\n");
> > >
> > > If the hardware doesn't support lane remapping for CCP2, then that should
> > > be reflected in DT bindings, i.e. data-lanes isn't relevant. There's no
> > > need to check that here.
> >
> > Should the above check for CSI-2 be dropped as well then ?
>
> Same for CSI-2, too: if there's nothing to configure there (lane remapping)
> there's no need to validate that part of the DT either.

OK, I'll drop that.

> > > > + goto cleanup_exit;
> > > > + }
> > > > +
> > > > + unicam->max_data_lanes = 1;
> > > > + unicam->active_data_lanes = 1;
> > > > + unicam->bus_flags = ep.bus.mipi_csi1.strobe;
> > > > + break;
> > > > +
> > > > + default:
> > > > + /* Unsupported bus type */
> > > > + dev_err(unicam->dev, "unsupported bus type %u\n",
> > > > + ep.bus_type);
> > > > + goto cleanup_exit;
> > > > + }
> > > > +
> > > > + dev_dbg(unicam->dev, "%s bus, %u data lanes, flags=0x%08x\n",
> > > > + unicam->bus_type == V4L2_MBUS_CSI2_DPHY ? "CSI-2" : "CCP2",
> > > > + unicam->active_data_lanes, unicam->bus_flags);
> > >
> > > V4l2-fwnode already prints this information I believe.
> >
> > True. It does so with pr_debug() though, it would be nice to use
> > dev_dbg(). That's a candidate for a separate fix of course.
>
> The reason for using pr_debug() is that the device isn't used by the fwnode
> framework. Would you add that just for debug prints? Note that the device
> nodes themselves are already being printed so it adds little to the
> usefulness of the messages.

I'll send patches, we can discuss their usefulness there.

--
Regards,

Laurent Pinchart

2023-07-02 22:34:14

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Hi Laurent,

On Mon, Jul 03, 2023 at 12:47:11AM +0300, Laurent Pinchart wrote:
> Hi Sakari,
>
> On Mon, Jul 03, 2023 at 12:45:07AM +0300, Laurent Pinchart wrote:
> > On Sun, Jul 02, 2023 at 06:18:21PM +0000, Sakari Ailus wrote:
> > > On Sun, Jul 02, 2023 at 06:23:56PM +0300, Laurent Pinchart wrote:
> > > > On Fri, Feb 25, 2022 at 11:29:18AM +0200, Sakari Ailus wrote:
> > > > > On Tue, Feb 08, 2022 at 04:50:20PM +0100, Jean-Michel Hautbois wrote:
> > > > > > Add driver for the Unicam camera receiver block on BCM283x processors.
> > > > > > It is represented as two video device nodes: unicam-image and
> > > > > > unicam-embedded which are connected to an internal subdev (named
> > > > > > unicam-subdev) in order to manage streams routing.
> > > > > >
> > > > > > Signed-off-by: Dave Stevenson <[email protected]>
> > > > > > Signed-off-by: Naushir Patuck <[email protected]>
> > > > > > Signed-off-by: Jean-Michel Hautbois <[email protected]>
> > > > > >
> > > > > > ---
> > > > > > v4:
> > > > > > - Add the vendor prefox for DT name
> > > > > > - Use the reg-names in DT parsing
> > > > > > - Remove MAINTAINERS entry
> > > > > >
> > > > > > v3 main changes:
> > > > > > - Change code organization
> > > > > > - Remove unused variables
> > > > > > - Correct the fmt_meta functions
> > > > > > - Rewrite the start/stop streaming
> > > > > > - You can now start the image node alone, but not the metadata one
> > > > > > - The buffers are allocated per-node
> > > > > > - only the required stream is started, if the route exists and is
> > > > > > enabled
> > > > > > - Prefix the macros with UNICAM_ to not have too generic names
> > > > > > - Drop colorspace support
> > > > > > -> This is causing issues in the try-fmt v4l2-compliance test
> > > > > > test VIDIOC_G_FMT: OK
> > > > > > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > > > > > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > > > > > test VIDIOC_TRY_FMT: FAIL
> > > > > > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > > > > > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > > > > > test VIDIOC_S_FMT: FAIL
> > > > > >
> > > > > > v2: Remove the unicam_{info,debug,error} macros and use
> > > > > > dev_dbg/dev_err instead.
> > > > > > ---
> > > > > > drivers/media/platform/Kconfig | 1 +
> > > > > > drivers/media/platform/Makefile | 2 +
> > > > > > drivers/media/platform/bcm2835/Kconfig | 21 +
> > > > > > drivers/media/platform/bcm2835/Makefile | 3 +
> > > > > > .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> > > > > > .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> > > > > > 6 files changed, 2850 insertions(+)
> > > > > > create mode 100644 drivers/media/platform/bcm2835/Kconfig
> > > > > > create mode 100644 drivers/media/platform/bcm2835/Makefile
> > > > > > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > > > > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c
> > > >
> > > > [snip]
> > > >
> > > > > > diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > > > > new file mode 100644
> > > > > > index 000000000000..b8d297076a02
> > > > > > --- /dev/null
> > > > > > +++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > >
> > > > [snip]
> > > >
> > > > > > +static int unicam_connect_of_subdevs(struct unicam_device *unicam)
> > > > > > +{
> > > > > > + struct v4l2_fwnode_endpoint ep = { };
> > > > > > + struct fwnode_handle *ep_handle;
> > > > > > + struct v4l2_async_subdev *asd;
> > > > > > + unsigned int lane;
> > > > > > + int ret = -EINVAL;
> > > > > > +
> > > > > > + if (of_property_read_u32(unicam->dev->of_node, "brcm,num-data-lanes",
> > > > > > + &unicam->max_data_lanes) < 0) {
> > > > >
> > > > > As you're already using fwnode API below, you could use
> > > > > device_property_read_u32() here.
> > > > >
> > > > > You can then replace of_device.h by mod_devicetable.h. Up to you.
> > > > >
> > > > > > + dev_err(unicam->dev, "DT property %s not set\n",
> > > > > > + "brcm,num-data-lanes");
> > > > > > + return -EINVAL;
> > > > > > + }
> > > > > > +
> > > > > > + /* Get the local endpoint and remote device. */
> > > > > > + ep_handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(unicam->dev),
> > > > > > + 0, 0,
> > > > > > + FWNODE_GRAPH_ENDPOINT_NEXT);
> > > > > > + if (!ep_handle) {
> > > > > > + dev_err(unicam->dev, "No endpoint\n");
> > > > > > + return -ENODEV;
> > > > > > + }
> > > > > > +
> > > > > > + /* Parse the local endpoint and validate its configuration. */
> > > > > > + if (v4l2_fwnode_endpoint_alloc_parse(ep_handle, &ep)) {
> > > > >
> > > > > As you don't need link-frequencies property parsing, you should use
> > > > > v4l2_fwnode_endpoint_parse(). That avoids having to call
> > > > > v4l2_fwnode_endpoint_free().
> > > > >
> > > > > > + dev_err(unicam->dev, "could not parse endpoint\n");
> > > > > > + goto cleanup_exit;
> > > > > > + }
> > > > > > +
> > > > > > + dev_dbg(unicam->dev, "parsed local endpoint, bus_type %u\n",
> > > > > > + ep.bus_type);
> > > > > > +
> > > > > > + unicam->bus_type = ep.bus_type;
> > > > > > +
> > > > > > + switch (ep.bus_type) {
> > > > > > + case V4L2_MBUS_CSI2_DPHY:
> > > > > > + switch (ep.bus.mipi_csi2.num_data_lanes) {
> > > > > > + case 1:
> > > > > > + case 2:
> > > > > > + case 4:
> > > > > > + break;
> > > > > > +
> > > > > > + default:
> > > > > > + dev_err(unicam->dev, "%u data lanes not supported\n",
> > > > > > + ep.bus.mipi_csi2.num_data_lanes);
> > > > > > + goto cleanup_exit;
> > > > > > + }
> > > > > > +
> > > > > > + for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
> > > > > > + if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
> > > > > > + dev_err(unicam->dev, "data lanes reordering not supported\n");
> > > > > > + goto cleanup_exit;
> > > > > > + }
> > > > > > + }
> > > > > > +
> > > > > > + if (ep.bus.mipi_csi2.num_data_lanes > unicam->max_data_lanes) {
> > > > > > + dev_err(unicam->dev, "endpoint requires %u data lanes when %u are supported\n",
> > > > > > + ep.bus.mipi_csi2.num_data_lanes,
> > > > > > + unicam->max_data_lanes);
> > > > > > + }
> > > > > > +
> > > > > > + unicam->active_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
> > > > > > + unicam->bus_flags = ep.bus.mipi_csi2.flags;
> > > > > > +
> > > > > > + break;
> > > > > > +
> > > > > > + case V4L2_MBUS_CCP2:
> > > > > > + if (ep.bus.mipi_csi1.clock_lane != 0 ||
> > > > > > + ep.bus.mipi_csi1.data_lane != 1) {
> > > > > > + dev_err(unicam->dev, "unsupported lanes configuration\n");
> > > > >
> > > > > If the hardware doesn't support lane remapping for CCP2, then that should
> > > > > be reflected in DT bindings, i.e. data-lanes isn't relevant. There's no
> > > > > need to check that here.
> > > >
> > > > Should the above check for CSI-2 be dropped as well then ?
> > >
> > > Same for CSI-2, too: if there's nothing to configure there (lane remapping)
> > > there's no need to validate that part of the DT either.
> >
> > OK, I'll drop that.
>
> Actually, I'm wondering if it would make sense to tell the parsing
> functions whether lane reordering is supported or not. The checks could
> then be moved to the framework. What do you think ?

I'm not sure how useful this check would be in the first place: if you have
hardware that can reorder the lanes, the framework doesn't know what to
check there (if anything) and otherwise there's little point in the
entire check.

--
Sakari Ailus

2023-07-02 22:43:39

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

On Mon, Jul 03, 2023 at 01:28:03AM +0300, Laurent Pinchart wrote:
> On Sun, Jul 02, 2023 at 10:20:29PM +0000, Sakari Ailus wrote:
> > On Mon, Jul 03, 2023 at 01:01:38AM +0300, Laurent Pinchart wrote:
> > > > > > > > > If the hardware doesn't support lane remapping for CCP2, then that should
> > > > > > > > > be reflected in DT bindings, i.e. data-lanes isn't relevant. There's no
> > > > > > > > > need to check that here.
> > > > > > > >
> > > > > > > > Should the above check for CSI-2 be dropped as well then ?
> > > > > > >
> > > > > > > Same for CSI-2, too: if there's nothing to configure there (lane remapping)
> > > > > > > there's no need to validate that part of the DT either.
> > > > > >
> > > > > > OK, I'll drop that.
> > > > >
> > > > > Actually, I'm wondering if it would make sense to tell the parsing
> > > > > functions whether lane reordering is supported or not. The checks could
> > > > > then be moved to the framework. What do you think ?
> > > >
> > > > I'm not sure how useful this check would be in the first place: if you have
> > > > hardware that can reorder the lanes, the framework doesn't know what to
> > > > check there (if anything) and otherwise there's little point in the
> > > > entire check.
> > >
> > > Isn't it good to tell users that something is wrong instead of accepting
> > > the invalid configuration and let them wonder why the device isn't
> > > working ? Users in this case would be system integrators, not end
> > > users, but we have lots of debugging information in the kernel aimed for
> > > them already.
> >
> > In which of the two cases above the framework could do something useful
> > there? For devices where you can reorder the lanes or for those where you
> > can't?
>
> For devices where you can't, to detect DT that reorders lanes.

I'd suggest to ignore that. The data-lanes property is only used to tell
the number of lanes in that case, even bindings just specify the number of
entries in general.

The parser notifies about that, though, if the numbers clash, using a debug
messages --- since it's not an error (or even worth warning about).

--
Sakari Ailus

2023-07-02 22:44:01

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

On Sun, Jul 02, 2023 at 09:53:52PM +0000, Sakari Ailus wrote:
> On Mon, Jul 03, 2023 at 12:45:05AM +0300, Laurent Pinchart wrote:
> > On Sun, Jul 02, 2023 at 06:18:21PM +0000, Sakari Ailus wrote:
> > > On Sun, Jul 02, 2023 at 06:23:56PM +0300, Laurent Pinchart wrote:
> > > > On Fri, Feb 25, 2022 at 11:29:18AM +0200, Sakari Ailus wrote:
> > > > > On Tue, Feb 08, 2022 at 04:50:20PM +0100, Jean-Michel Hautbois wrote:
> > > > > > Add driver for the Unicam camera receiver block on BCM283x processors.
> > > > > > It is represented as two video device nodes: unicam-image and
> > > > > > unicam-embedded which are connected to an internal subdev (named
> > > > > > unicam-subdev) in order to manage streams routing.
> > > > > >
> > > > > > Signed-off-by: Dave Stevenson <[email protected]>
> > > > > > Signed-off-by: Naushir Patuck <[email protected]>
> > > > > > Signed-off-by: Jean-Michel Hautbois <[email protected]>
> > > > > >
> > > > > > ---
> > > > > > v4:
> > > > > > - Add the vendor prefox for DT name
> > > > > > - Use the reg-names in DT parsing
> > > > > > - Remove MAINTAINERS entry
> > > > > >
> > > > > > v3 main changes:
> > > > > > - Change code organization
> > > > > > - Remove unused variables
> > > > > > - Correct the fmt_meta functions
> > > > > > - Rewrite the start/stop streaming
> > > > > > - You can now start the image node alone, but not the metadata one
> > > > > > - The buffers are allocated per-node
> > > > > > - only the required stream is started, if the route exists and is
> > > > > > enabled
> > > > > > - Prefix the macros with UNICAM_ to not have too generic names
> > > > > > - Drop colorspace support
> > > > > > -> This is causing issues in the try-fmt v4l2-compliance test
> > > > > > test VIDIOC_G_FMT: OK
> > > > > > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > > > > > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > > > > > test VIDIOC_TRY_FMT: FAIL
> > > > > > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > > > > > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > > > > > test VIDIOC_S_FMT: FAIL
> > > > > >
> > > > > > v2: Remove the unicam_{info,debug,error} macros and use
> > > > > > dev_dbg/dev_err instead.
> > > > > > ---
> > > > > > drivers/media/platform/Kconfig | 1 +
> > > > > > drivers/media/platform/Makefile | 2 +
> > > > > > drivers/media/platform/bcm2835/Kconfig | 21 +
> > > > > > drivers/media/platform/bcm2835/Makefile | 3 +
> > > > > > .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> > > > > > .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> > > > > > 6 files changed, 2850 insertions(+)
> > > > > > create mode 100644 drivers/media/platform/bcm2835/Kconfig
> > > > > > create mode 100644 drivers/media/platform/bcm2835/Makefile
> > > > > > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > > > > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c
> > > >
> > > > [snip]
> > > >
> > > > > > diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > > > > new file mode 100644
> > > > > > index 000000000000..b8d297076a02
> > > > > > --- /dev/null
> > > > > > +++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > >
> > > > [snip]
> > > >
> > > > > > +static int unicam_connect_of_subdevs(struct unicam_device *unicam)
> > > > > > +{
> > > > > > + struct v4l2_fwnode_endpoint ep = { };
> > > > > > + struct fwnode_handle *ep_handle;
> > > > > > + struct v4l2_async_subdev *asd;
> > > > > > + unsigned int lane;
> > > > > > + int ret = -EINVAL;
> > > > > > +
> > > > > > + if (of_property_read_u32(unicam->dev->of_node, "brcm,num-data-lanes",
> > > > > > + &unicam->max_data_lanes) < 0) {
> > > > >
> > > > > As you're already using fwnode API below, you could use
> > > > > device_property_read_u32() here.
> > > > >
> > > > > You can then replace of_device.h by mod_devicetable.h. Up to you.
> > > > >
> > > > > > + dev_err(unicam->dev, "DT property %s not set\n",
> > > > > > + "brcm,num-data-lanes");
> > > > > > + return -EINVAL;
> > > > > > + }
> > > > > > +
> > > > > > + /* Get the local endpoint and remote device. */
> > > > > > + ep_handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(unicam->dev),
> > > > > > + 0, 0,
> > > > > > + FWNODE_GRAPH_ENDPOINT_NEXT);
> > > > > > + if (!ep_handle) {
> > > > > > + dev_err(unicam->dev, "No endpoint\n");
> > > > > > + return -ENODEV;
> > > > > > + }
> > > > > > +
> > > > > > + /* Parse the local endpoint and validate its configuration. */
> > > > > > + if (v4l2_fwnode_endpoint_alloc_parse(ep_handle, &ep)) {
> > > > >
> > > > > As you don't need link-frequencies property parsing, you should use
> > > > > v4l2_fwnode_endpoint_parse(). That avoids having to call
> > > > > v4l2_fwnode_endpoint_free().
> > > > >
> > > > > > + dev_err(unicam->dev, "could not parse endpoint\n");
> > > > > > + goto cleanup_exit;
> > > > > > + }
> > > > > > +
> > > > > > + dev_dbg(unicam->dev, "parsed local endpoint, bus_type %u\n",
> > > > > > + ep.bus_type);
> > > > > > +
> > > > > > + unicam->bus_type = ep.bus_type;
> > > > > > +
> > > > > > + switch (ep.bus_type) {
> > > > > > + case V4L2_MBUS_CSI2_DPHY:
> > > > > > + switch (ep.bus.mipi_csi2.num_data_lanes) {
> > > > > > + case 1:
> > > > > > + case 2:
> > > > > > + case 4:
> > > > > > + break;
> > > > > > +
> > > > > > + default:
> > > > > > + dev_err(unicam->dev, "%u data lanes not supported\n",
> > > > > > + ep.bus.mipi_csi2.num_data_lanes);
> > > > > > + goto cleanup_exit;
> > > > > > + }
> > > > > > +
> > > > > > + for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
> > > > > > + if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
> > > > > > + dev_err(unicam->dev, "data lanes reordering not supported\n");
> > > > > > + goto cleanup_exit;
> > > > > > + }
> > > > > > + }
> > > > > > +
> > > > > > + if (ep.bus.mipi_csi2.num_data_lanes > unicam->max_data_lanes) {
> > > > > > + dev_err(unicam->dev, "endpoint requires %u data lanes when %u are supported\n",
> > > > > > + ep.bus.mipi_csi2.num_data_lanes,
> > > > > > + unicam->max_data_lanes);
> > > > > > + }
> > > > > > +
> > > > > > + unicam->active_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
> > > > > > + unicam->bus_flags = ep.bus.mipi_csi2.flags;
> > > > > > +
> > > > > > + break;
> > > > > > +
> > > > > > + case V4L2_MBUS_CCP2:
> > > > > > + if (ep.bus.mipi_csi1.clock_lane != 0 ||
> > > > > > + ep.bus.mipi_csi1.data_lane != 1) {
> > > > > > + dev_err(unicam->dev, "unsupported lanes configuration\n");
> > > > >
> > > > > If the hardware doesn't support lane remapping for CCP2, then that should
> > > > > be reflected in DT bindings, i.e. data-lanes isn't relevant. There's no
> > > > > need to check that here.
> > > >
> > > > Should the above check for CSI-2 be dropped as well then ?
> > >
> > > Same for CSI-2, too: if there's nothing to configure there (lane remapping)
> > > there's no need to validate that part of the DT either.
> >
> > OK, I'll drop that.
> >
> > > > > > + goto cleanup_exit;
> > > > > > + }
> > > > > > +
> > > > > > + unicam->max_data_lanes = 1;
> > > > > > + unicam->active_data_lanes = 1;
> > > > > > + unicam->bus_flags = ep.bus.mipi_csi1.strobe;
> > > > > > + break;
> > > > > > +
> > > > > > + default:
> > > > > > + /* Unsupported bus type */
> > > > > > + dev_err(unicam->dev, "unsupported bus type %u\n",
> > > > > > + ep.bus_type);
> > > > > > + goto cleanup_exit;
> > > > > > + }
> > > > > > +
> > > > > > + dev_dbg(unicam->dev, "%s bus, %u data lanes, flags=0x%08x\n",
> > > > > > + unicam->bus_type == V4L2_MBUS_CSI2_DPHY ? "CSI-2" : "CCP2",
> > > > > > + unicam->active_data_lanes, unicam->bus_flags);
> > > > >
> > > > > V4l2-fwnode already prints this information I believe.
> > > >
> > > > True. It does so with pr_debug() though, it would be nice to use
> > > > dev_dbg(). That's a candidate for a separate fix of course.
> > >
> > > The reason for using pr_debug() is that the device isn't used by the fwnode
> > > framework. Would you add that just for debug prints? Note that the device
> > > nodes themselves are already being printed so it adds little to the
> > > usefulness of the messages.
> >
> > I'll send patches, we can discuss their usefulness there.
>
> I presume this would change to a few APIs, including the users?
>
> In order to spare time, how about discussing it first? :-)

I had already written the patches, so I can as well post them :-)

--
Regards,

Laurent Pinchart

2023-07-02 22:46:48

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

On Mon, Jul 03, 2023 at 12:45:05AM +0300, Laurent Pinchart wrote:
> Hi Sakari,
>
> On Sun, Jul 02, 2023 at 06:18:21PM +0000, Sakari Ailus wrote:
> > On Sun, Jul 02, 2023 at 06:23:56PM +0300, Laurent Pinchart wrote:
> > > On Fri, Feb 25, 2022 at 11:29:18AM +0200, Sakari Ailus wrote:
> > > > On Tue, Feb 08, 2022 at 04:50:20PM +0100, Jean-Michel Hautbois wrote:
> > > > > Add driver for the Unicam camera receiver block on BCM283x processors.
> > > > > It is represented as two video device nodes: unicam-image and
> > > > > unicam-embedded which are connected to an internal subdev (named
> > > > > unicam-subdev) in order to manage streams routing.
> > > > >
> > > > > Signed-off-by: Dave Stevenson <[email protected]>
> > > > > Signed-off-by: Naushir Patuck <[email protected]>
> > > > > Signed-off-by: Jean-Michel Hautbois <[email protected]>
> > > > >
> > > > > ---
> > > > > v4:
> > > > > - Add the vendor prefox for DT name
> > > > > - Use the reg-names in DT parsing
> > > > > - Remove MAINTAINERS entry
> > > > >
> > > > > v3 main changes:
> > > > > - Change code organization
> > > > > - Remove unused variables
> > > > > - Correct the fmt_meta functions
> > > > > - Rewrite the start/stop streaming
> > > > > - You can now start the image node alone, but not the metadata one
> > > > > - The buffers are allocated per-node
> > > > > - only the required stream is started, if the route exists and is
> > > > > enabled
> > > > > - Prefix the macros with UNICAM_ to not have too generic names
> > > > > - Drop colorspace support
> > > > > -> This is causing issues in the try-fmt v4l2-compliance test
> > > > > test VIDIOC_G_FMT: OK
> > > > > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > > > > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > > > > test VIDIOC_TRY_FMT: FAIL
> > > > > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > > > > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > > > > test VIDIOC_S_FMT: FAIL
> > > > >
> > > > > v2: Remove the unicam_{info,debug,error} macros and use
> > > > > dev_dbg/dev_err instead.
> > > > > ---
> > > > > drivers/media/platform/Kconfig | 1 +
> > > > > drivers/media/platform/Makefile | 2 +
> > > > > drivers/media/platform/bcm2835/Kconfig | 21 +
> > > > > drivers/media/platform/bcm2835/Makefile | 3 +
> > > > > .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> > > > > .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> > > > > 6 files changed, 2850 insertions(+)
> > > > > create mode 100644 drivers/media/platform/bcm2835/Kconfig
> > > > > create mode 100644 drivers/media/platform/bcm2835/Makefile
> > > > > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > > > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c
> > >
> > > [snip]
> > >
> > > > > diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > > > new file mode 100644
> > > > > index 000000000000..b8d297076a02
> > > > > --- /dev/null
> > > > > +++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > >
> > > [snip]
> > >
> > > > > +static int unicam_connect_of_subdevs(struct unicam_device *unicam)
> > > > > +{
> > > > > + struct v4l2_fwnode_endpoint ep = { };
> > > > > + struct fwnode_handle *ep_handle;
> > > > > + struct v4l2_async_subdev *asd;
> > > > > + unsigned int lane;
> > > > > + int ret = -EINVAL;
> > > > > +
> > > > > + if (of_property_read_u32(unicam->dev->of_node, "brcm,num-data-lanes",
> > > > > + &unicam->max_data_lanes) < 0) {
> > > >
> > > > As you're already using fwnode API below, you could use
> > > > device_property_read_u32() here.
> > > >
> > > > You can then replace of_device.h by mod_devicetable.h. Up to you.
> > > >
> > > > > + dev_err(unicam->dev, "DT property %s not set\n",
> > > > > + "brcm,num-data-lanes");
> > > > > + return -EINVAL;
> > > > > + }
> > > > > +
> > > > > + /* Get the local endpoint and remote device. */
> > > > > + ep_handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(unicam->dev),
> > > > > + 0, 0,
> > > > > + FWNODE_GRAPH_ENDPOINT_NEXT);
> > > > > + if (!ep_handle) {
> > > > > + dev_err(unicam->dev, "No endpoint\n");
> > > > > + return -ENODEV;
> > > > > + }
> > > > > +
> > > > > + /* Parse the local endpoint and validate its configuration. */
> > > > > + if (v4l2_fwnode_endpoint_alloc_parse(ep_handle, &ep)) {
> > > >
> > > > As you don't need link-frequencies property parsing, you should use
> > > > v4l2_fwnode_endpoint_parse(). That avoids having to call
> > > > v4l2_fwnode_endpoint_free().
> > > >
> > > > > + dev_err(unicam->dev, "could not parse endpoint\n");
> > > > > + goto cleanup_exit;
> > > > > + }
> > > > > +
> > > > > + dev_dbg(unicam->dev, "parsed local endpoint, bus_type %u\n",
> > > > > + ep.bus_type);
> > > > > +
> > > > > + unicam->bus_type = ep.bus_type;
> > > > > +
> > > > > + switch (ep.bus_type) {
> > > > > + case V4L2_MBUS_CSI2_DPHY:
> > > > > + switch (ep.bus.mipi_csi2.num_data_lanes) {
> > > > > + case 1:
> > > > > + case 2:
> > > > > + case 4:
> > > > > + break;
> > > > > +
> > > > > + default:
> > > > > + dev_err(unicam->dev, "%u data lanes not supported\n",
> > > > > + ep.bus.mipi_csi2.num_data_lanes);
> > > > > + goto cleanup_exit;
> > > > > + }
> > > > > +
> > > > > + for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
> > > > > + if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
> > > > > + dev_err(unicam->dev, "data lanes reordering not supported\n");
> > > > > + goto cleanup_exit;
> > > > > + }
> > > > > + }
> > > > > +
> > > > > + if (ep.bus.mipi_csi2.num_data_lanes > unicam->max_data_lanes) {
> > > > > + dev_err(unicam->dev, "endpoint requires %u data lanes when %u are supported\n",
> > > > > + ep.bus.mipi_csi2.num_data_lanes,
> > > > > + unicam->max_data_lanes);
> > > > > + }
> > > > > +
> > > > > + unicam->active_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
> > > > > + unicam->bus_flags = ep.bus.mipi_csi2.flags;
> > > > > +
> > > > > + break;
> > > > > +
> > > > > + case V4L2_MBUS_CCP2:
> > > > > + if (ep.bus.mipi_csi1.clock_lane != 0 ||
> > > > > + ep.bus.mipi_csi1.data_lane != 1) {
> > > > > + dev_err(unicam->dev, "unsupported lanes configuration\n");
> > > >
> > > > If the hardware doesn't support lane remapping for CCP2, then that should
> > > > be reflected in DT bindings, i.e. data-lanes isn't relevant. There's no
> > > > need to check that here.
> > >
> > > Should the above check for CSI-2 be dropped as well then ?
> >
> > Same for CSI-2, too: if there's nothing to configure there (lane remapping)
> > there's no need to validate that part of the DT either.
>
> OK, I'll drop that.
>
> > > > > + goto cleanup_exit;
> > > > > + }
> > > > > +
> > > > > + unicam->max_data_lanes = 1;
> > > > > + unicam->active_data_lanes = 1;
> > > > > + unicam->bus_flags = ep.bus.mipi_csi1.strobe;
> > > > > + break;
> > > > > +
> > > > > + default:
> > > > > + /* Unsupported bus type */
> > > > > + dev_err(unicam->dev, "unsupported bus type %u\n",
> > > > > + ep.bus_type);
> > > > > + goto cleanup_exit;
> > > > > + }
> > > > > +
> > > > > + dev_dbg(unicam->dev, "%s bus, %u data lanes, flags=0x%08x\n",
> > > > > + unicam->bus_type == V4L2_MBUS_CSI2_DPHY ? "CSI-2" : "CCP2",
> > > > > + unicam->active_data_lanes, unicam->bus_flags);
> > > >
> > > > V4l2-fwnode already prints this information I believe.
> > >
> > > True. It does so with pr_debug() though, it would be nice to use
> > > dev_dbg(). That's a candidate for a separate fix of course.
> >
> > The reason for using pr_debug() is that the device isn't used by the fwnode
> > framework. Would you add that just for debug prints? Note that the device
> > nodes themselves are already being printed so it adds little to the
> > usefulness of the messages.
>
> I'll send patches, we can discuss their usefulness there.

I presume this would change to a few APIs, including the users?

In order to spare time, how about discussing it first? :-)

--
Sakari Ailus

2023-07-02 22:49:47

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

On Sun, Jul 02, 2023 at 10:20:29PM +0000, Sakari Ailus wrote:
> On Mon, Jul 03, 2023 at 01:01:38AM +0300, Laurent Pinchart wrote:
> > > > > > > > If the hardware doesn't support lane remapping for CCP2, then that should
> > > > > > > > be reflected in DT bindings, i.e. data-lanes isn't relevant. There's no
> > > > > > > > need to check that here.
> > > > > > >
> > > > > > > Should the above check for CSI-2 be dropped as well then ?
> > > > > >
> > > > > > Same for CSI-2, too: if there's nothing to configure there (lane remapping)
> > > > > > there's no need to validate that part of the DT either.
> > > > >
> > > > > OK, I'll drop that.
> > > >
> > > > Actually, I'm wondering if it would make sense to tell the parsing
> > > > functions whether lane reordering is supported or not. The checks could
> > > > then be moved to the framework. What do you think ?
> > >
> > > I'm not sure how useful this check would be in the first place: if you have
> > > hardware that can reorder the lanes, the framework doesn't know what to
> > > check there (if anything) and otherwise there's little point in the
> > > entire check.
> >
> > Isn't it good to tell users that something is wrong instead of accepting
> > the invalid configuration and let them wonder why the device isn't
> > working ? Users in this case would be system integrators, not end
> > users, but we have lots of debugging information in the kernel aimed for
> > them already.
>
> In which of the two cases above the framework could do something useful
> there? For devices where you can reorder the lanes or for those where you
> can't?

For devices where you can't, to detect DT that reorders lanes.

--
Regards,

Laurent Pinchart

2023-07-02 22:53:34

by Sakari Ailus

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Hi Laurent,

On Mon, Jul 03, 2023 at 01:01:38AM +0300, Laurent Pinchart wrote:
> > > > > > > If the hardware doesn't support lane remapping for CCP2, then that should
> > > > > > > be reflected in DT bindings, i.e. data-lanes isn't relevant. There's no
> > > > > > > need to check that here.
> > > > > >
> > > > > > Should the above check for CSI-2 be dropped as well then ?
> > > > >
> > > > > Same for CSI-2, too: if there's nothing to configure there (lane remapping)
> > > > > there's no need to validate that part of the DT either.
> > > >
> > > > OK, I'll drop that.
> > >
> > > Actually, I'm wondering if it would make sense to tell the parsing
> > > functions whether lane reordering is supported or not. The checks could
> > > then be moved to the framework. What do you think ?
> >
> > I'm not sure how useful this check would be in the first place: if you have
> > hardware that can reorder the lanes, the framework doesn't know what to
> > check there (if anything) and otherwise there's little point in the
> > entire check.
>
> Isn't it good to tell users that something is wrong instead of accepting
> the invalid configuration and let them wonder why the device isn't
> working ? Users in this case would be system integrators, not end
> users, but we have lots of debugging information in the kernel aimed for
> them already.

In which of the two cases above the framework could do something useful
there? For devices where you can reorder the lanes or for those where you
can't?

--
Sakari Ailus

2023-07-02 23:51:20

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v5 04/11] media: bcm2835-unicam: Add support for CCP2/CSI2 camera interface

Hi Sakari,

On Sun, Jul 02, 2023 at 09:56:11PM +0000, Sakari Ailus wrote:
> On Mon, Jul 03, 2023 at 12:47:11AM +0300, Laurent Pinchart wrote:
> > On Mon, Jul 03, 2023 at 12:45:07AM +0300, Laurent Pinchart wrote:
> > > On Sun, Jul 02, 2023 at 06:18:21PM +0000, Sakari Ailus wrote:
> > > > On Sun, Jul 02, 2023 at 06:23:56PM +0300, Laurent Pinchart wrote:
> > > > > On Fri, Feb 25, 2022 at 11:29:18AM +0200, Sakari Ailus wrote:
> > > > > > On Tue, Feb 08, 2022 at 04:50:20PM +0100, Jean-Michel Hautbois wrote:
> > > > > > > Add driver for the Unicam camera receiver block on BCM283x processors.
> > > > > > > It is represented as two video device nodes: unicam-image and
> > > > > > > unicam-embedded which are connected to an internal subdev (named
> > > > > > > unicam-subdev) in order to manage streams routing.
> > > > > > >
> > > > > > > Signed-off-by: Dave Stevenson <[email protected]>
> > > > > > > Signed-off-by: Naushir Patuck <[email protected]>
> > > > > > > Signed-off-by: Jean-Michel Hautbois <[email protected]>
> > > > > > >
> > > > > > > ---
> > > > > > > v4:
> > > > > > > - Add the vendor prefox for DT name
> > > > > > > - Use the reg-names in DT parsing
> > > > > > > - Remove MAINTAINERS entry
> > > > > > >
> > > > > > > v3 main changes:
> > > > > > > - Change code organization
> > > > > > > - Remove unused variables
> > > > > > > - Correct the fmt_meta functions
> > > > > > > - Rewrite the start/stop streaming
> > > > > > > - You can now start the image node alone, but not the metadata one
> > > > > > > - The buffers are allocated per-node
> > > > > > > - only the required stream is started, if the route exists and is
> > > > > > > enabled
> > > > > > > - Prefix the macros with UNICAM_ to not have too generic names
> > > > > > > - Drop colorspace support
> > > > > > > -> This is causing issues in the try-fmt v4l2-compliance test
> > > > > > > test VIDIOC_G_FMT: OK
> > > > > > > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > > > > > > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > > > > > > test VIDIOC_TRY_FMT: FAIL
> > > > > > > fail: v4l2-test-formats.cpp(363): colorspace >= 0xff
> > > > > > > fail: v4l2-test-formats.cpp(465): testColorspace(!node->is_io_mc, pix.pixelformat, pix.colorspace, pix.ycbcr_enc, pix.quantization)
> > > > > > > test VIDIOC_S_FMT: FAIL
> > > > > > >
> > > > > > > v2: Remove the unicam_{info,debug,error} macros and use
> > > > > > > dev_dbg/dev_err instead.
> > > > > > > ---
> > > > > > > drivers/media/platform/Kconfig | 1 +
> > > > > > > drivers/media/platform/Makefile | 2 +
> > > > > > > drivers/media/platform/bcm2835/Kconfig | 21 +
> > > > > > > drivers/media/platform/bcm2835/Makefile | 3 +
> > > > > > > .../platform/bcm2835/bcm2835-unicam-regs.h | 253 ++
> > > > > > > .../media/platform/bcm2835/bcm2835-unicam.c | 2570 +++++++++++++++++
> > > > > > > 6 files changed, 2850 insertions(+)
> > > > > > > create mode 100644 drivers/media/platform/bcm2835/Kconfig
> > > > > > > create mode 100644 drivers/media/platform/bcm2835/Makefile
> > > > > > > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > > > > > create mode 100644 drivers/media/platform/bcm2835/bcm2835-unicam.c
> > > > >
> > > > > [snip]
> > > > >
> > > > > > > diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > > > > > new file mode 100644
> > > > > > > index 000000000000..b8d297076a02
> > > > > > > --- /dev/null
> > > > > > > +++ b/drivers/media/platform/bcm2835/bcm2835-unicam-regs.h
> > > > >
> > > > > [snip]
> > > > >
> > > > > > > +static int unicam_connect_of_subdevs(struct unicam_device *unicam)
> > > > > > > +{
> > > > > > > + struct v4l2_fwnode_endpoint ep = { };
> > > > > > > + struct fwnode_handle *ep_handle;
> > > > > > > + struct v4l2_async_subdev *asd;
> > > > > > > + unsigned int lane;
> > > > > > > + int ret = -EINVAL;
> > > > > > > +
> > > > > > > + if (of_property_read_u32(unicam->dev->of_node, "brcm,num-data-lanes",
> > > > > > > + &unicam->max_data_lanes) < 0) {
> > > > > >
> > > > > > As you're already using fwnode API below, you could use
> > > > > > device_property_read_u32() here.
> > > > > >
> > > > > > You can then replace of_device.h by mod_devicetable.h. Up to you.
> > > > > >
> > > > > > > + dev_err(unicam->dev, "DT property %s not set\n",
> > > > > > > + "brcm,num-data-lanes");
> > > > > > > + return -EINVAL;
> > > > > > > + }
> > > > > > > +
> > > > > > > + /* Get the local endpoint and remote device. */
> > > > > > > + ep_handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(unicam->dev),
> > > > > > > + 0, 0,
> > > > > > > + FWNODE_GRAPH_ENDPOINT_NEXT);
> > > > > > > + if (!ep_handle) {
> > > > > > > + dev_err(unicam->dev, "No endpoint\n");
> > > > > > > + return -ENODEV;
> > > > > > > + }
> > > > > > > +
> > > > > > > + /* Parse the local endpoint and validate its configuration. */
> > > > > > > + if (v4l2_fwnode_endpoint_alloc_parse(ep_handle, &ep)) {
> > > > > >
> > > > > > As you don't need link-frequencies property parsing, you should use
> > > > > > v4l2_fwnode_endpoint_parse(). That avoids having to call
> > > > > > v4l2_fwnode_endpoint_free().
> > > > > >
> > > > > > > + dev_err(unicam->dev, "could not parse endpoint\n");
> > > > > > > + goto cleanup_exit;
> > > > > > > + }
> > > > > > > +
> > > > > > > + dev_dbg(unicam->dev, "parsed local endpoint, bus_type %u\n",
> > > > > > > + ep.bus_type);
> > > > > > > +
> > > > > > > + unicam->bus_type = ep.bus_type;
> > > > > > > +
> > > > > > > + switch (ep.bus_type) {
> > > > > > > + case V4L2_MBUS_CSI2_DPHY:
> > > > > > > + switch (ep.bus.mipi_csi2.num_data_lanes) {
> > > > > > > + case 1:
> > > > > > > + case 2:
> > > > > > > + case 4:
> > > > > > > + break;
> > > > > > > +
> > > > > > > + default:
> > > > > > > + dev_err(unicam->dev, "%u data lanes not supported\n",
> > > > > > > + ep.bus.mipi_csi2.num_data_lanes);
> > > > > > > + goto cleanup_exit;
> > > > > > > + }
> > > > > > > +
> > > > > > > + for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
> > > > > > > + if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
> > > > > > > + dev_err(unicam->dev, "data lanes reordering not supported\n");
> > > > > > > + goto cleanup_exit;
> > > > > > > + }
> > > > > > > + }
> > > > > > > +
> > > > > > > + if (ep.bus.mipi_csi2.num_data_lanes > unicam->max_data_lanes) {
> > > > > > > + dev_err(unicam->dev, "endpoint requires %u data lanes when %u are supported\n",
> > > > > > > + ep.bus.mipi_csi2.num_data_lanes,
> > > > > > > + unicam->max_data_lanes);
> > > > > > > + }
> > > > > > > +
> > > > > > > + unicam->active_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
> > > > > > > + unicam->bus_flags = ep.bus.mipi_csi2.flags;
> > > > > > > +
> > > > > > > + break;
> > > > > > > +
> > > > > > > + case V4L2_MBUS_CCP2:
> > > > > > > + if (ep.bus.mipi_csi1.clock_lane != 0 ||
> > > > > > > + ep.bus.mipi_csi1.data_lane != 1) {
> > > > > > > + dev_err(unicam->dev, "unsupported lanes configuration\n");
> > > > > >
> > > > > > If the hardware doesn't support lane remapping for CCP2, then that should
> > > > > > be reflected in DT bindings, i.e. data-lanes isn't relevant. There's no
> > > > > > need to check that here.
> > > > >
> > > > > Should the above check for CSI-2 be dropped as well then ?
> > > >
> > > > Same for CSI-2, too: if there's nothing to configure there (lane remapping)
> > > > there's no need to validate that part of the DT either.
> > >
> > > OK, I'll drop that.
> >
> > Actually, I'm wondering if it would make sense to tell the parsing
> > functions whether lane reordering is supported or not. The checks could
> > then be moved to the framework. What do you think ?
>
> I'm not sure how useful this check would be in the first place: if you have
> hardware that can reorder the lanes, the framework doesn't know what to
> check there (if anything) and otherwise there's little point in the
> entire check.

Isn't it good to tell users that something is wrong instead of accepting
the invalid configuration and let them wonder why the device isn't
working ? Users in this case would be system integrators, not end
users, but we have lots of debugging information in the kernel aimed for
them already.

--
Regards,

Laurent Pinchart