[PATCH 4/4] media: rcar-isp: ispcore: Add NV12M support

From: Niklas Söderlund

Date: Mon Oct 19 2026 - 15:38:07 EST


It is possible to support NV12M output by only outputting the CbCr plane
from even or odd lines. Enabling support for it is straightforward but
one snag around how the DMA-engine works.

When the DMA-engine is configured to only output CbCr data from odd or
even lines it still outputs blanks pixels to the buffer for the line
where no data is taken.

To work around this and still produce valid NV12 frames the device needs
to be configured so all blank pixels written to the buffer are part of
the line padding. In addition to also keeping the same number of padding
bytes as the Y plane.

The DMA-engine is able to do this as we can set different strides for
the two planes and haw two DMA-engine lines (one with data + some
padding and one with only padding) make up a single correctly formatted
NV12 CbCr line.

For readers comparing the DMA configuration with the datasheet this
driver takes the CbCr data from even lines, compared to odd lines in the
datasheet example. The reason for this is that if odd lines are used the
first even line adds half a NV12 line of padding at the start of the
CbCr plane. To counter this the CbCr plane would need a padding line at
the start of the plane which is not supported, or we would need to set
the plane start address half a line before the buffer actually starts
and potentially corrupt memory as we write blank bytes outside our
buffer.

Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@xxxxxxxxxxxx>
---
drivers/media/platform/renesas/rcar-isp/core-io.c | 39 +++++++++++++++++++++-
drivers/media/platform/renesas/rcar-isp/core.c | 40 +++++++++++++++++++++++
2 files changed, 78 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/renesas/rcar-isp/core-io.c b/drivers/media/platform/renesas/rcar-isp/core-io.c
index 48db37bad69c..a9c522212e9e 100644
--- a/drivers/media/platform/renesas/rcar-isp/core-io.c
+++ b/drivers/media/platform/renesas/rcar-isp/core-io.c
@@ -768,6 +768,39 @@ static void risp_io_capture_try_format(struct rcar_isp_core_io *io,
pix->xfer_func = V4L2_XFER_FUNC_SRGB;

switch (pix->pixelformat) {
+ case V4L2_PIX_FMT_NV12M:
+ pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
+ pix->num_planes = 2;
+ /*
+ * The DMA-engine still outputs pixels for the height lines of
+ * the CrCb plane. To produce a NV12 frame we must ensure all
+ * these blank pixels are in the padding area of the frame.
+ *
+ * To do this the stride of the CrCb plane need to be half that
+ * of the Y plane. The first "DMA-engine line" will write the
+ * CrCb data + some blank padding, while the second "DMA-engine
+ * line" will only write blank padding bytes. Two "DMA-engine
+ * lines" will then create one correct NV12 line in the CrCb
+ * plane.
+ *
+ * All frame data also needs to fits in the first half of the
+ * line. The strict DMA flush settings used already ensures
+ * this is always the case.
+ *
+ * Finally the NV12 stride of the Y and CrCb plane must be the
+ * same and the R-Car ISP needs the hardware stride to be 256
+ * byte aligned. By align the Y plane lines to 512 bytes the
+ * CrCb planes hardware stride is always aligned with 256 when
+ * cut in half.
+ */
+ pix->plane_fmt[0].bytesperline =
+ ALIGN(risp_io_calture_bytesperline(pix->width, 1), 512);
+ pix->plane_fmt[0].sizeimage = pix->plane_fmt[0].bytesperline * pix->height;
+ /* Both planes must have the same length. */
+ pix->plane_fmt[1].bytesperline = pix->plane_fmt[0].bytesperline;
+ /* The CrCb plane is half height of the Y plane. */
+ pix->plane_fmt[1].sizeimage = pix->plane_fmt[1].bytesperline * pix->height / 2;
+ break;
case V4L2_PIX_FMT_NV16M:
pix->quantization = V4L2_QUANTIZATION_LIM_RANGE;
pix->num_planes = 2;
@@ -794,9 +827,12 @@ static int risp_io_capture_enum_fmt(struct file *file, void *priv,

switch (f->index) {
case 0:
- f->pixelformat = V4L2_PIX_FMT_NV16M;
+ f->pixelformat = V4L2_PIX_FMT_NV12M;
break;
case 1:
+ f->pixelformat = V4L2_PIX_FMT_NV16M;
+ break;
+ case 2:
f->pixelformat = V4L2_PIX_FMT_XBGR32;
break;
default:
@@ -854,6 +890,7 @@ static int risp_io_capture_enum_framesizes(struct file *file, void *fh,
return -EINVAL;

switch (fsize->pixel_format) {
+ case V4L2_PIX_FMT_NV12M:
case V4L2_PIX_FMT_NV16M:
case V4L2_PIX_FMT_XBGR32:
break;
diff --git a/drivers/media/platform/renesas/rcar-isp/core.c b/drivers/media/platform/renesas/rcar-isp/core.c
index cb9d9033d0cf..9be2f73394ee 100644
--- a/drivers/media/platform/renesas/rcar-isp/core.c
+++ b/drivers/media/platform/renesas/rcar-isp/core.c
@@ -341,6 +341,45 @@ static int risp_core_config_output(struct rcar_isp_core *core,
/* For all frame capture slots. */
for (unsigned int frame = 0; frame < 4; frame++) {
switch (pix->pixelformat) {
+ case V4L2_PIX_FMT_NV12M:
+ risp_core_write(core,
+ ISPCORE_DMA_IMAGE_FRAME_MODE(index, frame),
+ 1);
+ risp_core_write(core,
+ ISPCORE_DMA_IMAGE_FRAME_PIXEL_POSITION(index, frame),
+ 0 << 24 | 0 << 16 | 4 << 8 | 16 << 0);
+ risp_core_write(core,
+ ISPCORE_DMA_IMAGE_FRAME_PIXEL_BITWIDTH_MINUS1(index, frame),
+ 0 << 24 | 0 << 16 | 7 << 8 | 7 << 0);
+ /*
+ * Pick CrCb data from even lines.
+ *
+ * The datasheet suggest taking the odd lines but that
+ * would require padding at the start of the buffer to
+ * create valid NV12 frame, or start writing outside the
+ * buffer potentially corrupting memory.
+ */
+ risp_core_write(core,
+ ISPCORE_DMA_IMAGE_FRAME_PIXEL_BPP(index, frame),
+ 0 << 28 | 0 << 24 |
+ 0 << 20 | 0 << 16 |
+ 1 << 12 | 0 << 8 |
+ 3 << 4 | 0 << 0);
+
+ risp_core_write(core,
+ ISPCORE_DMA_IMAGE_FRAME_STRIDE_COMP0(index, frame),
+ pix->plane_fmt[0].bytesperline);
+ /*
+ * Stride of CrCb plane needs be cut in half as the
+ * DMA-engine writes blank pixels for the lines not
+ * processed.
+ *
+ * See risp_io_capture_try_format() for details.
+ */
+ risp_core_write(core,
+ ISPCORE_DMA_IMAGE_FRAME_STRIDE_COMP1(index, frame),
+ pix->plane_fmt[1].bytesperline / 2);
+ break;
case V4L2_PIX_FMT_NV16M:
risp_core_write(core,
ISPCORE_DMA_IMAGE_FRAME_MODE(index, frame),
@@ -436,6 +475,7 @@ static u32 risp_core_pix2bus(const struct rcar_isp_core_io *io)
return MEDIA_BUS_FMT_SRGGB12_1X12;
case V4L2_PIX_FMT_XBGR32:
return MEDIA_BUS_FMT_RGB888_1X24;
+ case V4L2_PIX_FMT_NV12M:
case V4L2_PIX_FMT_NV16M:
return MEDIA_BUS_FMT_YUYV12_1X24;
default:

--
2.55.0