| Message ID | 20251205-mali-cru-v1-1-d81bb5ffe73a@ideasonboard.com |
|---|---|
| State | Superseded |
| Headers | show |
| Series |
|
| Related | show |
Hi 2025. 12. 05. 15:52 keltezéssel, Jacopo Mondi írta: > 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(-) > > [...] > + > +void RZG2LCRU::cruReturnBuffer(FrameBuffer *buffer) > +{ > + for (const std::unique_ptr<FrameBuffer> &buf : buffers_) { > + if (buf.get() == buffer) { > + availableBuffers_.push(buffer); > + break; > + } > + } I am bit confused by this. Is it expected that sometimes `buffer` will not be in `buffers_`? Is that not an error case? It would be a bit more clear to me like this: auto it = std::find_if(buffers_.begin(), buffers_.end(), [&](const auto& b) { return b.get() == buffer; }); // or similar // ASSERT(it != buffers_.end()) // if (it == buffers_.end()) return; // etc. availableBuffers_.push(buffer); > +} > [...] > +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 resolutiond -> resolutions > + * largest than the maximum supported CSI-2 receiver input size. largest -> larger > + */ > + 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")); Maybe have `static const std::regex csi2Regex("...");` somewhere? > + 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]")); Same here. > + 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 @@ > [...] > +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(); An empty line is missing for me here. Regards, Barnabás Pőcze > +private: > + void freeBuffers(); > + > [...]
Hi Barnabás On Mon, Dec 08, 2025 at 10:48:40AM +0100, Barnabás Pőcze wrote: > Hi > > 2025. 12. 05. 15:52 keltezéssel, Jacopo Mondi írta: > > 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(-) > > > > [...] > > + > > +void RZG2LCRU::cruReturnBuffer(FrameBuffer *buffer) > > +{ > > + for (const std::unique_ptr<FrameBuffer> &buf : buffers_) { > > + if (buf.get() == buffer) { > > + availableBuffers_.push(buffer); > > + break; > > + } > > + } > > I am bit confused by this. Is it expected that sometimes `buffer` will > not be in `buffers_`? Is that not an error case? I don't see how it can happen, and if it happens is a programming mistake that's worth an assertion imho > > It would be a bit more clear to me like this: > > auto it = std::find_if(buffers_.begin(), buffers_.end(), [&](const auto& b) { return b.get() == buffer; }); // or similar > > // ASSERT(it != buffers_.end()) yes! > // if (it == buffers_.end()) return; > // etc. > > availableBuffers_.push(buffer); > > > > +} > > [...] > > +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 > > resolutiond -> resolutions > > > > + * largest than the maximum supported CSI-2 receiver input size. > > largest -> larger > Thanks > > > + */ > > + 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")); > > Maybe have `static const std::regex csi2Regex("...");` somewhere? > ok > > > + 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]")); > > Same here. > > > > + 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 @@ > > [...] > > +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(); > > An empty line is missing for me here. yes it is! thanks > > > Regards, > Barnabás Pőcze > > > > +private: > > + void freeBuffers(); > > + > > [...] >
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 */