@@ -1,5 +1,6 @@
# SPDX-License-Identifier: CC0-1.0
libcamera_internal_sources += files([
- 'mali-c55.cpp'
+ 'mali-c55.cpp',
+ 'rzg2l-cru.cpp',
])
new file mode 100644
@@ -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 */
new file mode 100644
@@ -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 */