[1/7] libcamera: mali-c55: Add RZG2LCRU class
diff mbox series

Message ID 20251205-mali-cru-v1-1-d81bb5ffe73a@ideasonboard.com
State New
Headers show
Series
  • libcamera: mali-c55: Add support for memory-to-memory
Related show

Commit Message

Jacopo Mondi Dec. 5, 2025, 2:52 p.m. UTC
From: Daniel Scally <dan.scally@ideasonboard.com>

Add a class allowing us to control the RZ/G2L Camera Receiver Unit.

The class is named after the media device it handles, which is called
"rzg2l_cru" but the CRU is actually found in other SoCs than the RZ/G2L
one, such as the RZ/V2H(P).

The RZG2LCRU class models the CSI-2 receiver found on those SoCs and
allows to add support for memory-to-memory operations on the RZ/V2H(P)
SoC which integrates a Mali-C55 ISP and a specialized DMA engine named
IVC that feeds images from memory, where the CRU has saved them.

Signed-off-by: Daniel Scally <dan.scally@ideasonboard.com>
Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
---
 src/libcamera/pipeline/mali-c55/meson.build   |   3 +-
 src/libcamera/pipeline/mali-c55/rzg2l-cru.cpp | 261 ++++++++++++++++++++++++++
 src/libcamera/pipeline/mali-c55/rzg2l-cru.h   |  72 +++++++
 3 files changed, 335 insertions(+), 1 deletion(-)

Patch
diff mbox series

diff --git a/src/libcamera/pipeline/mali-c55/meson.build b/src/libcamera/pipeline/mali-c55/meson.build
index eba8e5a3905469297ade6ed5bf523473448cb9de..4e768242602e563612dca7ccbd0d11f5e5ab92a5 100644
--- a/src/libcamera/pipeline/mali-c55/meson.build
+++ b/src/libcamera/pipeline/mali-c55/meson.build
@@ -1,5 +1,6 @@ 
 # SPDX-License-Identifier: CC0-1.0
 
 libcamera_internal_sources += files([
-    'mali-c55.cpp'
+    'mali-c55.cpp',
+    'rzg2l-cru.cpp',
 ])
diff --git a/src/libcamera/pipeline/mali-c55/rzg2l-cru.cpp b/src/libcamera/pipeline/mali-c55/rzg2l-cru.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ac540ba2e29d25191e87a6208d33094e4ef2b969
--- /dev/null
+++ b/src/libcamera/pipeline/mali-c55/rzg2l-cru.cpp
@@ -0,0 +1,261 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2025, Ideas on Board Oy
+ *
+ * Pipine handler element for the Renesas RZ/G2L Camera Receiver Unit
+ */
+
+#include "rzg2l-cru.h"
+
+#include <algorithm>
+#include <map>
+
+#include <linux/videodev2.h>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/regex.h>
+
+#include <libcamera/formats.h>
+#include <libcamera/pixel_format.h>
+
+#include "libcamera/internal/bayer_format.h"
+#include "libcamera/internal/camera_sensor.h"
+#include "libcamera/internal/framebuffer.h"
+#include "libcamera/internal/media_device.h"
+#include "libcamera/internal/request.h"
+
+namespace libcamera {
+
+static const std::map<uint8_t, V4L2PixelFormat> bitDepthToFmt{
+	{ 10, V4L2PixelFormat(V4L2_PIX_FMT_RAW_CRU10) },
+	{ 12, V4L2PixelFormat(V4L2_PIX_FMT_RAW_CRU12) },
+	{ 14, V4L2PixelFormat(V4L2_PIX_FMT_RAW_CRU14) },
+};
+
+LOG_DEFINE_CATEGORY(RZG2LCRU)
+
+FrameBuffer *RZG2LCRU::queueBuffer(Request *request)
+{
+	FrameBuffer *buffer;
+
+	if (availableBuffers_.empty()) {
+		LOG(RZG2LCRU, Debug) << "CRU buffer underrun";
+		return nullptr;
+	}
+
+	buffer = availableBuffers_.front();
+
+	int ret = output_->queueBuffer(buffer);
+	if (ret) {
+		LOG(RZG2LCRU, Error) << "Failed to queue buffer to CRU";
+		return nullptr;
+	}
+
+	availableBuffers_.pop();
+	buffer->_d()->setRequest(request);
+
+	return buffer;
+}
+
+void RZG2LCRU::cruReturnBuffer(FrameBuffer *buffer)
+{
+	for (const std::unique_ptr<FrameBuffer> &buf : buffers_) {
+		if (buf.get() == buffer) {
+			availableBuffers_.push(buffer);
+			break;
+		}
+	}
+}
+
+int RZG2LCRU::start()
+{
+	int ret = output_->exportBuffers(kBufferCount, &buffers_);
+	if (ret < 0)
+		return ret;
+
+	ret = output_->importBuffers(kBufferCount);
+	if (ret)
+		return ret;
+
+	for (std::unique_ptr<FrameBuffer> &buffer : buffers_)
+		availableBuffers_.push(buffer.get());
+
+	ret = output_->streamOn();
+	if (ret) {
+		freeBuffers();
+		return ret;
+	}
+
+	return 0;
+}
+
+int RZG2LCRU::stop()
+{
+	int ret;
+
+	csi2_->setFrameStartEnabled(false);
+
+	ret = output_->streamOff();
+
+	freeBuffers();
+
+	return ret;
+}
+
+void RZG2LCRU::freeBuffers()
+{
+	availableBuffers_ = {};
+	buffers_.clear();
+
+	if (output_->releaseBuffers())
+		LOG(RZG2LCRU, Error) << "Failed to release CRU buffers";
+}
+
+int RZG2LCRU::configure(V4L2SubdeviceFormat *subdevFormat, V4L2DeviceFormat *inputFormat)
+{
+	/*
+	 * Set format on the sensor and propagate it up to the CRU video
+	 * device.
+	 */
+
+	int ret = sensor_->setFormat(subdevFormat);
+	if (ret)
+		return ret;
+
+	ret = csi2_->setFormat(0, subdevFormat);
+	if (ret)
+		return ret;
+
+	ret = csi2_->getFormat(1, subdevFormat);
+	if (ret)
+		return ret;
+
+	ret = cru_->setFormat(0, subdevFormat);
+	if (ret)
+		return ret;
+
+	ret = cru_->getFormat(1, subdevFormat);
+	if (ret)
+		return ret;
+
+	/*
+	 * The capture device needs to be set with a format that can be produced
+	 * from the mbus code of the subdevFormat. The CRU and IVC use bayer
+	 * order agnostic pixel formats, so all we need to do is find the right
+	 * bitdepth and select the appropriate format.
+	 */
+	BayerFormat bayerFormat = BayerFormat::fromMbusCode(subdevFormat->code);
+	if (!bayerFormat.isValid())
+		return -EINVAL;
+
+	V4L2DeviceFormat captureFormat;
+	captureFormat.fourcc = bitDepthToFmt.at(bayerFormat.bitDepth);
+	captureFormat.size = subdevFormat->size;
+
+	ret = output_->setFormat(&captureFormat);
+	if (ret)
+		return ret;
+
+	/*
+	 * We return the format that we set against the output device, as the
+	 * same format will also need to be set against the Input Video Control
+	 * Block device.
+	 */
+	*inputFormat = captureFormat;
+
+	return 0;
+}
+
+void RZG2LCRU::initCRUSizes()
+{
+	Size maxCSI2Size;
+
+	/*
+	 * Get the maximum supported size on the CSI-2 receiver. We need to
+	 * query the kernel interface as the size limits differ from RZ/G2L
+	 * (2800x4095) and RZ/V2H (4096x4096).
+	 */
+	V4L2Subdevice::Formats csi2Formats = csi2_->formats(0);
+	if (csi2Formats.empty())
+		return;
+
+	for (const auto &format : csi2Formats) {
+		for (const auto &range : format.second) {
+			if (range.max > maxCSI2Size)
+				maxCSI2Size = range.max;
+		}
+	}
+
+	/*
+	 * Enumerate the sensor supported resolutiond and filter out the ones
+	 * largest than the maximum supported CSI-2 receiver input size.
+	 */
+	V4L2Subdevice::Formats formats = sensor_->device()->formats(0);
+	if (formats.empty())
+		return;
+
+	for (const auto &format : formats) {
+		for (const auto &range : format.second) {
+			const Size &max = range.max;
+
+			if (max.width > maxCSI2Size.width ||
+			    max.height > maxCSI2Size.height)
+				continue;
+
+			csi2Sizes_.push_back(max);
+		}
+	}
+
+	/* Sort in increasing order and remove duplicates. */
+	std::sort(csi2Sizes_.begin(), csi2Sizes_.end());
+	auto last = std::unique(csi2Sizes_.begin(), csi2Sizes_.end());
+	csi2Sizes_.erase(last, csi2Sizes_.end());
+
+	csi2Resolution_ = csi2Sizes_.back();
+}
+
+int RZG2LCRU::init(const MediaDevice *media)
+{
+	int ret;
+
+	csi2_ = V4L2Subdevice::fromEntityName(media,
+					      std::regex("csi-[0-9a-f]{8}.csi2"));
+	if (!csi2_)
+		return -ENODEV;
+
+	ret = csi2_->open();
+	if (ret)
+		return ret;
+
+	const std::vector<MediaPad *> &pads = csi2_->entity()->pads();
+	if (pads.empty())
+		return -ENODEV;
+
+	/* The receiver has a single sink pad at index 0 */
+	MediaPad *sink = pads[0];
+	const std::vector<MediaLink *> &links = sink->links();
+	if (links.empty())
+		return -ENODEV;
+
+	MediaLink *link = links[0];
+	sensor_ = CameraSensorFactoryBase::create(link->source()->entity());
+	if (!sensor_)
+		return -ENODEV;
+
+	cru_ = V4L2Subdevice::fromEntityName(media,
+					     std::regex("cru-ip-[0-9a-f]{8}.cru[0-9]"));
+	ret = cru_->open();
+	if (ret)
+		return ret;
+
+	output_ = V4L2VideoDevice::fromEntityName(media, "CRU output");
+	ret = output_->open();
+	if (ret)
+		return ret;
+
+	initCRUSizes();
+
+	return 0;
+}
+
+} /* namespace libcamera */
diff --git a/src/libcamera/pipeline/mali-c55/rzg2l-cru.h b/src/libcamera/pipeline/mali-c55/rzg2l-cru.h
new file mode 100644
index 0000000000000000000000000000000000000000..9944e71cda82a494182619f01766f32f57a8b516
--- /dev/null
+++ b/src/libcamera/pipeline/mali-c55/rzg2l-cru.h
@@ -0,0 +1,72 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2025, Ideas on Board Oy
+ *
+ * Pipine handler element for the Renesas RZ/G2L Camera Receiver Unit
+ */
+
+#pragma once
+
+#include <memory>
+#include <queue>
+#include <vector>
+
+#include <libcamera/base/signal.h>
+
+#include "libcamera/internal/v4l2_subdevice.h"
+#include "libcamera/internal/v4l2_videodevice.h"
+
+namespace libcamera {
+
+class CameraSensor;
+class FrameBuffer;
+class MediaDevice;
+class Request;
+class Size;
+
+class RZG2LCRU
+{
+public:
+	static constexpr unsigned int kBufferCount = 4;
+
+	RZG2LCRU() = default;
+
+	const std::vector<Size> &sizes() const
+	{
+		return csi2Sizes_;
+	}
+
+	int init(const MediaDevice *media);
+	const Size &resolution() const
+	{
+		return csi2Resolution_;
+	}
+
+	CameraSensor *sensor() const { return sensor_.get(); }
+
+	int configure(V4L2SubdeviceFormat *subdevFormat, V4L2DeviceFormat *inputFormat);
+	FrameBuffer *queueBuffer(Request *request);
+	void cruReturnBuffer(FrameBuffer *buffer);
+	V4L2VideoDevice *output() { return output_.get(); }
+	int start();
+	int stop();
+private:
+	void freeBuffers();
+
+	void cruBufferReady(FrameBuffer *buffer);
+
+	void initCRUSizes();
+
+	std::unique_ptr<CameraSensor> sensor_;
+	std::unique_ptr<V4L2Subdevice> csi2_;
+	std::unique_ptr<V4L2Subdevice> cru_;
+	std::unique_ptr<V4L2VideoDevice> output_;
+
+	std::vector<std::unique_ptr<FrameBuffer>> buffers_;
+	std::queue<FrameBuffer *> availableBuffers_;
+
+	std::vector<Size> csi2Sizes_;
+	Size csi2Resolution_;
+};
+
+} /* namespace libcamera */