[11/14] ipa: rppx1: Add RPP-X1 IPA skeleton
diff mbox series

Message ID 20260618-rppx1-ipa-v1-11-32337264cfcd@ideasonboard.com
State New
Headers show
Series
  • libcamera: Add support for R-Car Gen4 and RPP-X1 ISP
Related show

Commit Message

Jacopo Mondi June 18, 2026, 10:18 a.m. UTC
Add the infrastructure for the RPP-X1 without any registered algorithm
but with the definition of the IPA module interface and the IPA context.

Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
---
 Documentation/Doxyfile-common.in  |   1 +
 include/libcamera/ipa/meson.build |   1 +
 include/libcamera/ipa/rppx1.mojom |  41 ++++++
 meson_options.txt                 |   4 +-
 src/ipa/meson.build               |   1 +
 src/ipa/rppx1/ipa_context.cpp     |  63 +++++++++
 src/ipa/rppx1/ipa_context.h       |  55 ++++++++
 src/ipa/rppx1/meson.build         |  26 ++++
 src/ipa/rppx1/module.h            |  27 ++++
 src/ipa/rppx1/rppx1.cpp           | 280 ++++++++++++++++++++++++++++++++++++++
 10 files changed, 497 insertions(+), 2 deletions(-)

Comments

Niklas Söderlund June 19, 2026, 3:09 p.m. UTC | #1
Hi Jacopo,

Thanks for your work.

On 2026-06-18 12:18:50 +0200, Jacopo Mondi wrote:
> Add the infrastructure for the RPP-X1 without any registered algorithm
> but with the definition of the IPA module interface and the IPA context.
> 
> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> ---
>  Documentation/Doxyfile-common.in  |   1 +
>  include/libcamera/ipa/meson.build |   1 +
>  include/libcamera/ipa/rppx1.mojom |  41 ++++++
>  meson_options.txt                 |   4 +-
>  src/ipa/meson.build               |   1 +
>  src/ipa/rppx1/ipa_context.cpp     |  63 +++++++++
>  src/ipa/rppx1/ipa_context.h       |  55 ++++++++
>  src/ipa/rppx1/meson.build         |  26 ++++
>  src/ipa/rppx1/module.h            |  27 ++++
>  src/ipa/rppx1/rppx1.cpp           | 280 ++++++++++++++++++++++++++++++++++++++
>  10 files changed, 497 insertions(+), 2 deletions(-)
> 
> diff --git a/Documentation/Doxyfile-common.in b/Documentation/Doxyfile-common.in
> index f11ec593d5ae..131fdcc6083f 100644
> --- a/Documentation/Doxyfile-common.in
> +++ b/Documentation/Doxyfile-common.in
> @@ -37,6 +37,7 @@ EXCLUDE_PATTERNS       = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \
>                           @TOP_BUILDDIR@/include/libcamera/ipa/mali-c55_*.h \
>                           @TOP_BUILDDIR@/include/libcamera/ipa/raspberrypi_*.h \
>                           @TOP_BUILDDIR@/include/libcamera/ipa/rkisp1_*.h \
> +                         @TOP_BUILDDIR@/include/libcamera/ipa/rppx1_*.h \
>                           @TOP_BUILDDIR@/include/libcamera/ipa/vimc_*.h
>  
>  EXCLUDE_SYMBOLS        = libcamera::BoundMethodArgs \
> diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build
> index 3ee3ada303c0..9ac15546102f 100644
> --- a/include/libcamera/ipa/meson.build
> +++ b/include/libcamera/ipa/meson.build
> @@ -66,6 +66,7 @@ pipeline_ipa_mojom_mapping = {
>      'ipu3': 'ipu3.mojom',
>      'mali-c55': 'mali-c55.mojom',
>      'rkisp1': 'rkisp1.mojom',
> +    'rcar-gen4': 'rppx1.mojom',
>      'rpi/pisp': 'raspberrypi.mojom',
>      'rpi/vc4': 'raspberrypi.mojom',
>      'simple': 'soft.mojom',
> diff --git a/include/libcamera/ipa/rppx1.mojom b/include/libcamera/ipa/rppx1.mojom
> new file mode 100644
> index 000000000000..4abbebb24f85
> --- /dev/null
> +++ b/include/libcamera/ipa/rppx1.mojom
> @@ -0,0 +1,41 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +
> +/*
> + * \todo Document the interface and remove the related EXCLUDE_PATTERNS entry.
> + */
> +
> +module ipa.rppx1;
> +
> +import "include/libcamera/ipa/core.mojom";
> +
> +struct IPAConfigInfo {
> +	libcamera.IPACameraSensorInfo sensorInfo;
> +	libcamera.ControlInfoMap sensorControls;
> +};
> +
> +interface IPARppX1Interface {
> +	init(libcamera.IPASettings settings,
> +	     libcamera.IPACameraSensorInfo sensorInfo,
> +	     libcamera.ControlInfoMap sensorControls)
> +		=> (int32 ret, libcamera.ControlInfoMap ipaControls);
> +	start() => (int32 ret);
> +	stop();
> +
> +	configure(IPAConfigInfo configInfo,
> +		  map<uint32, libcamera.IPAStream> streamConfig)
> +		=> (int32 ret, libcamera.ControlInfoMap ipaControls);
> +
> +	mapBuffers(array<libcamera.IPABuffer> buffers);
> +	unmapBuffers(array<uint32> ids);
> +
> +	[async] queueRequest(uint32 frame, libcamera.ControlList reqControls);
> +	[async] computeParams(uint32 frame, uint32 bufferId);
> +	[async] processStats(uint32 frame, uint32 bufferId,
> +			     libcamera.ControlList sensorControls);
> +};
> +
> +interface IPARppX1EventInterface {
> +	paramsComputed(uint32 frame, uint32 bytesused);
> +	setSensorControls(uint32 frame, libcamera.ControlList sensorControls);
> +	metadataReady(uint32 frame, libcamera.ControlList metadata);
> +};
> diff --git a/meson_options.txt b/meson_options.txt
> index 20baacc4fc65..0aa3aef24edd 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -48,8 +48,8 @@ option('gstreamer',
>  
>  option('ipas',
>          type : 'array',
> -        choices : ['ipu3', 'mali-c55', 'rkisp1', 'rpi/pisp', 'rpi/vc4', 'simple',
> -                   'vimc'],
> +        choices : ['ipu3', 'mali-c55', 'rkisp1', 'rppx1', 'rpi/pisp', 'rpi/vc4',
> +                   'simple', 'vimc'],

nit: rppx1 should be sorted after rpi/*, no?

With this considered,

Reviewed-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

>          description : 'Select which IPA modules to build')
>  
>  option('lc-compliance',
> diff --git a/src/ipa/meson.build b/src/ipa/meson.build
> index c583c7efdd35..411d043021f8 100644
> --- a/src/ipa/meson.build
> +++ b/src/ipa/meson.build
> @@ -27,6 +27,7 @@ ipa_sign = files('ipa-sign.sh')
>  supported_ipas = {
>      'ipu3':       'ipu3',
>      'mali-c55':   'mali-c55',
> +    'rcar-gen4':  'rppx1',
>      'rkisp1':     'rkisp1',
>      'rpi/pisp':   'rpi/pisp',
>      'rpi/vc4':    'rpi/vc4',
> diff --git a/src/ipa/rppx1/ipa_context.cpp b/src/ipa/rppx1/ipa_context.cpp
> new file mode 100644
> index 000000000000..5ac60dfc7bf6
> --- /dev/null
> +++ b/src/ipa/rppx1/ipa_context.cpp
> @@ -0,0 +1,63 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2026 Renesas Electronics Corp.
> + * Copyright (C) 2026 Ideas on Board Oy
> + * Copyright (C) 2026 Ragnatech AB
> + *
> + * RPP-X1 IPA Context
> + */
> +
> +#include "ipa_context.h"
> +
> +/**
> + * \file ipa_context.h
> + * \brief Context and state information shared between the algorithms
> + */
> +
> +namespace libcamera::ipa::rppx1 {
> +
> +/**
> + * \struct IPASessionConfiguration
> + * \brief Session configuration for the IPA module
> + */
> +
> +/**
> + * \struct IPAActiveState
> + * \brief Active state for algorithms
> + */
> +
> +/**
> + * \struct IPAFrameContext
> + * \brief Per-frame context for algorithms
> + */
> +
> +/**
> + * \struct IPAContext
> + * \brief Global IPA context data shared between all algorithms
> + *
> + * \var IPAContext::sensorInfo
> + * \brief The IPA session sensorInfo, immutable during the session
> + *
> + * \var IPAContext::configuration
> + * \brief The IPA session configuration, immutable during the session
> + *
> + * \var IPAContext::activeState
> + * \brief The IPA active state, storing the latest state for all algorithms
> + *
> + * \var IPAContext::frameContexts
> + * \brief Ring buffer of per-frame contexts
> + *
> + * \var IPAContext::ctrlMap
> + * \brief The IPA map of controls
> + *
> + * \var IPAContext::camHelper
> + * \brief The IPA camera helper
> + */
> +
> +/**
> + * \fn IPAContext::IPAContext
> + * \brief Construct the IPA context
> + * \param[in] frameContextSize Size of the frame context queue
> + */
> +
> +} /* namespace libcamera::ipa::rppx1 */
> diff --git a/src/ipa/rppx1/ipa_context.h b/src/ipa/rppx1/ipa_context.h
> new file mode 100644
> index 000000000000..f268a35081bc
> --- /dev/null
> +++ b/src/ipa/rppx1/ipa_context.h
> @@ -0,0 +1,55 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2026 Renesas Electronics Corp.
> + * Copyright (C) 2026 Ideas on Board Oy
> + * Copyright (C) 2026 Ragnatech AB
> + *
> + * RPP-X1 IPA Context
> + */
> +
> +#pragma once
> +
> +#include <memory>
> +
> +#include <linux/media/dreamchip/rppx1-config.h>
> +
> +#include <libcamera/controls.h>
> +
> +#include <libcamera/ipa/core_ipa_interface.h>
> +
> +#include <libipa/camera_sensor_helper.h>
> +#include <libipa/fc_queue.h>
> +
> +namespace libcamera {
> +
> +namespace ipa::rppx1 {
> +
> +struct IPASessionConfiguration {
> +};
> +
> +struct IPAActiveState {
> +};
> +
> +struct IPAFrameContext : public FrameContext {
> +};
> +
> +struct IPAContext {
> +	IPAContext(unsigned int frameContextSize)
> +		: frameContexts(frameContextSize)
> +	{
> +	}
> +
> +	IPACameraSensorInfo sensorInfo;
> +	IPASessionConfiguration configuration;
> +	IPAActiveState activeState;
> +
> +	FCQueue<IPAFrameContext> frameContexts;
> +
> +	ControlInfoMap::Map ctrlMap;
> +
> +	std::unique_ptr<CameraSensorHelper> camHelper;
> +};
> +
> +} /* namespace ipa::rppx1 */
> +
> +} /* namespace libcamera*/
> diff --git a/src/ipa/rppx1/meson.build b/src/ipa/rppx1/meson.build
> new file mode 100644
> index 000000000000..8034fe24d241
> --- /dev/null
> +++ b/src/ipa/rppx1/meson.build
> @@ -0,0 +1,26 @@
> +# SPDX-License-Identifier: CC0-1.0
> +
> +ipa_name = 'ipa_rppx1'
> +
> +rppx1_ipa_sources = files([
> +    'ipa_context.cpp',
> +    'rppx1.cpp',
> +])
> +
> +mod = shared_module(ipa_name, rppx1_ipa_sources,
> +                    name_prefix : '',
> +                    include_directories : [ipa_includes],
> +                    dependencies : [libcamera_private, libipa_dep],
> +                    install : true,
> +                    install_dir : ipa_install_dir)
> +
> +if ipa_sign_module
> +    custom_target(ipa_name + '.so.sign',
> +                  input : mod,
> +                  output : ipa_name + '.so.sign',
> +                  command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'],
> +                  install : false,
> +                  build_by_default : true)
> +endif
> +
> +ipa_names += ipa_name
> diff --git a/src/ipa/rppx1/module.h b/src/ipa/rppx1/module.h
> new file mode 100644
> index 000000000000..0bacff64de8f
> --- /dev/null
> +++ b/src/ipa/rppx1/module.h
> @@ -0,0 +1,27 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2026 Renesas Electronics Corp.
> + * Copyright (C) 2026 Ideas on Board Oy
> + * Copyright (C) 2026 Ragnatech AB
> + *
> + * RPP-X1 IPA Module
> + */
> +
> +#pragma once
> +
> +#include <libipa/module.h>
> +
> +#include "ipa_context.h"
> +#include "params.h"
> +#include "stats.h"
> +
> +namespace libcamera {
> +
> +namespace ipa::rppx1 {
> +
> +using Module = ipa::Module<IPAContext, IPAFrameContext, IPACameraSensorInfo,
> +			   RppX1Params, RppX1Stats>;
> +
> +} /* namespace ipa::rppx1 */
> +
> +} /* namespace libcamera*/
> diff --git a/src/ipa/rppx1/rppx1.cpp b/src/ipa/rppx1/rppx1.cpp
> new file mode 100644
> index 000000000000..20ca2d292704
> --- /dev/null
> +++ b/src/ipa/rppx1/rppx1.cpp
> @@ -0,0 +1,280 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2026 Renesas Electronics Corp.
> + * Copyright (C) 2026 Ideas on Board Oy
> + * Copyright (C) 2026 Ragnatech AB
> + *
> + * RPP-X1 Image Processing Algorithms
> + */
> +
> +#include <algorithm>
> +#include <stdint.h>
> +#include <string.h>
> +
> +#include <libcamera/base/file.h>
> +#include <libcamera/base/log.h>
> +
> +#include <libcamera/controls.h>
> +#include <libcamera/framebuffer.h>
> +
> +#include <libcamera/ipa/ipa_interface.h>
> +#include <libcamera/ipa/ipa_module_info.h>
> +#include <libcamera/ipa/rppx1_ipa_interface.h>
> +
> +#include "libcamera/internal/formats.h"
> +#include "libcamera/internal/mapped_framebuffer.h"
> +#include "libcamera/internal/yaml_parser.h"
> +
> +#include "algorithms/algorithm.h"
> +
> +#include "ipa_context.h"
> +#include "params.h"
> +#include "stats.h"
> +
> +namespace libcamera {
> +
> +LOG_DEFINE_CATEGORY(IPARppX1)
> +
> +namespace ipa::rppx1 {
> +
> +/* Maximum number of frame contexts to be held */
> +static constexpr uint32_t kMaxFrameContexts = 16;
> +
> +class IPARppX1 : public IPARppX1Interface, public Module
> +{
> +public:
> +	IPARppX1();
> +
> +	int init(const IPASettings &settings,
> +		 const IPACameraSensorInfo &sensorInfo,
> +		 const ControlInfoMap &sensorControls,
> +		 ControlInfoMap *ipaControls) override;
> +	int start() override;
> +	void stop() override;
> +
> +	int configure(const IPAConfigInfo &ipaConfig,
> +		      const std::map<uint32_t, IPAStream> &streamConfig,
> +		      ControlInfoMap *ipaControls) override;
> +	void mapBuffers(const std::vector<IPABuffer> &buffers) override;
> +	void unmapBuffers(const std::vector<unsigned int> &ids) override;
> +
> +	void queueRequest(const uint32_t frame, const ControlList &controls) override;
> +	void computeParams(const uint32_t frame, const uint32_t bufferId) override;
> +	void processStats(const uint32_t frame, const uint32_t bufferId,
> +			  const ControlList &sensorControls) override;
> +
> +protected:
> +	std::string logPrefix() const override;
> +
> +private:
> +	void updateControls(ControlInfoMap *ipaControls);
> +
> +	std::map<unsigned int, FrameBuffer> buffers_;
> +	std::map<unsigned int, MappedFrameBuffer> mappedBuffers_;
> +
> +	/* Local parameter storage */
> +	struct IPAContext context_;
> +};
> +
> +IPARppX1::IPARppX1()
> +	: context_(kMaxFrameContexts)
> +{
> +}
> +
> +std::string IPARppX1::logPrefix() const
> +{
> +	return "rppx1";
> +}
> +
> +void IPARppX1::updateControls(ControlInfoMap *ipaControls)
> +{
> +	ControlInfoMap::Map ctrlMap;
> +
> +	ctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end());
> +	*ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls);
> +}
> +
> +int IPARppX1::init(const IPASettings &settings,
> +		   const IPACameraSensorInfo &sensorInfo,
> +		   [[maybe_unused]] const ControlInfoMap &sensorControls,
> +		   [[maybe_unused]] ControlInfoMap *ipaControls)
> +{
> +	context_.sensorInfo = sensorInfo;
> +
> +	context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel);
> +	if (!context_.camHelper) {
> +		LOG(IPARppX1, Error)
> +			<< "Failed to create camera sensor helper for "
> +			<< settings.sensorModel;
> +		return -ENODEV;
> +	}
> +
> +	/* Load the tuning data file. */
> +	File file(settings.configurationFile);
> +	if (!file.open(File::OpenModeFlag::ReadOnly)) {
> +		int ret = file.error();
> +		LOG(IPARppX1, Error)
> +			<< "Failed to open configuration file "
> +			<< settings.configurationFile << ": " << strerror(-ret);
> +		return ret;
> +	}
> +
> +	std::unique_ptr<libcamera::ValueNode> data = YamlParser::parse(file);
> +	if (!data)
> +		return -EINVAL;
> +
> +	unsigned int version = (*data)["version"].get<uint32_t>(0);
> +	if (version != 1) {
> +		LOG(IPARppX1, Error)
> +			<< "Invalid tuning file version " << version;
> +		return -EINVAL;
> +	}
> +
> +	if (!data->contains("algorithms")) {
> +		LOG(IPARppX1, Error)
> +			<< "Tuning file doesn't contain any algorithm";
> +		return -EINVAL;
> +	}
> +
> +	int ret = createAlgorithms(context_, (*data)["algorithms"]);
> +	if (ret)
> +		return ret;
> +
> +	/* Initialize controls. */
> +	updateControls(ipaControls);
> +
> +	return 0;
> +}
> +
> +int IPARppX1::start()
> +{
> +	/* \todo Properly handle startup controls. */
> +	return 0;
> +}
> +
> +void IPARppX1::stop()
> +{
> +	context_.frameContexts.clear();
> +}
> +
> +int IPARppX1::configure(const IPAConfigInfo &ipaConfig,
> +			[[maybe_unused]] const std::map<uint32_t, IPAStream> &streamConfig,
> +			[[maybe_unused]] ControlInfoMap *ipaControls)
> +{
> +	/* Clear the IPA context before the streaming session. */
> +	context_.configuration = {};
> +	context_.activeState = {};
> +	context_.frameContexts.clear();
> +
> +	const IPACameraSensorInfo &info = ipaConfig.sensorInfo;
> +
> +	/* Update the camera controls using the new sensor settings. */
> +	updateControls(ipaControls);
> +
> +	for (auto const &a : algorithms()) {
> +		Algorithm *algo = static_cast<Algorithm *>(a.get());
> +
> +		int ret = algo->configure(context_, info);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +void IPARppX1::mapBuffers(const std::vector<IPABuffer> &buffers)
> +{
> +	for (const IPABuffer &buffer : buffers) {
> +		auto elem = buffers_.emplace(std::piecewise_construct,
> +					     std::forward_as_tuple(buffer.id),
> +					     std::forward_as_tuple(buffer.planes));
> +		const FrameBuffer &fb = elem.first->second;
> +
> +		MappedFrameBuffer mappedBuffer(&fb, MappedFrameBuffer::MapFlag::ReadWrite);
> +		if (!mappedBuffer.isValid()) {
> +			LOG(IPARppX1, Fatal) << "Failed to mmap buffer: "
> +					     << strerror(mappedBuffer.error());
> +		}
> +
> +		mappedBuffers_.emplace(buffer.id, std::move(mappedBuffer));
> +	}
> +}
> +
> +void IPARppX1::unmapBuffers(const std::vector<unsigned int> &ids)
> +{
> +	for (unsigned int id : ids) {
> +		const auto fb = buffers_.find(id);
> +		if (fb == buffers_.end())
> +			continue;
> +
> +		mappedBuffers_.erase(id);
> +		buffers_.erase(id);
> +	}
> +}
> +
> +void IPARppX1::queueRequest(const uint32_t frame, const ControlList &controls)
> +{
> +	IPAFrameContext &frameContext = context_.frameContexts.alloc(frame);
> +
> +	for (auto const &a : algorithms()) {
> +		Algorithm *algo = static_cast<Algorithm *>(a.get());
> +		if (algo->disabled_)
> +			continue;
> +
> +		algo->queueRequest(context_, frame, frameContext, controls);
> +	}
> +}
> +
> +void IPARppX1::computeParams(const uint32_t frame, const uint32_t bufferId)
> +{
> +	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
> +
> +	RppX1Params params(mappedBuffers_.at(bufferId).planes()[0]);
> +
> +	for (auto const &algo : algorithms())
> +		algo->prepare(context_, frame, frameContext, &params);
> +
> +	paramsComputed.emit(frame, params.bytesused());
> +}
> +
> +void IPARppX1::processStats(const uint32_t frame, const uint32_t bufferId,
> +			    [[maybe_unused]] const ControlList &sensorControls)
> +{
> +	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
> +
> +	ControlList metadata(controls::controls);
> +
> +	auto stats = RppX1Stats(mappedBuffers_.at(bufferId).planes()[0]);
> +	if (!stats)
> +		return;
> +
> +	for (auto const &a : algorithms()) {
> +		Algorithm *algo = static_cast<Algorithm *>(a.get());
> +		if (algo->disabled_)
> +			continue;
> +		algo->process(context_, frame, frameContext, &stats, metadata);
> +	}
> +
> +	metadataReady.emit(frame, metadata);
> +}
> +
> +} /* namespace ipa::rppx1 */
> +
> +/*
> + * External IPA module interface
> + */
> +
> +extern "C" {
> +const struct IPAModuleInfo ipaModuleInfo = {
> +	IPA_MODULE_API_VERSION,
> +	1,
> +	"rppx1",
> +};
> +
> +IPAInterface *ipaCreate()
> +{
> +	return new ipa::rppx1::IPARppX1();
> +}
> +}
> +
> +} /* namespace libcamera */
> 
> -- 
> 2.54.0
>
Jacopo Mondi June 22, 2026, 12:13 p.m. UTC | #2
Hi Niklas

On Fri, Jun 19, 2026 at 05:09:55PM +0200, Niklas Söderlund wrote:
> Hi Jacopo,
>
> Thanks for your work.
>
> On 2026-06-18 12:18:50 +0200, Jacopo Mondi wrote:
> > Add the infrastructure for the RPP-X1 without any registered algorithm
> > but with the definition of the IPA module interface and the IPA context.
> >
> > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
> > ---
> >  Documentation/Doxyfile-common.in  |   1 +
> >  include/libcamera/ipa/meson.build |   1 +
> >  include/libcamera/ipa/rppx1.mojom |  41 ++++++
> >  meson_options.txt                 |   4 +-
> >  src/ipa/meson.build               |   1 +
> >  src/ipa/rppx1/ipa_context.cpp     |  63 +++++++++
> >  src/ipa/rppx1/ipa_context.h       |  55 ++++++++
> >  src/ipa/rppx1/meson.build         |  26 ++++
> >  src/ipa/rppx1/module.h            |  27 ++++
> >  src/ipa/rppx1/rppx1.cpp           | 280 ++++++++++++++++++++++++++++++++++++++
> >  10 files changed, 497 insertions(+), 2 deletions(-)
> >
> > diff --git a/Documentation/Doxyfile-common.in b/Documentation/Doxyfile-common.in
> > index f11ec593d5ae..131fdcc6083f 100644
> > --- a/Documentation/Doxyfile-common.in
> > +++ b/Documentation/Doxyfile-common.in
> > @@ -37,6 +37,7 @@ EXCLUDE_PATTERNS       = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \
> >                           @TOP_BUILDDIR@/include/libcamera/ipa/mali-c55_*.h \
> >                           @TOP_BUILDDIR@/include/libcamera/ipa/raspberrypi_*.h \
> >                           @TOP_BUILDDIR@/include/libcamera/ipa/rkisp1_*.h \
> > +                         @TOP_BUILDDIR@/include/libcamera/ipa/rppx1_*.h \
> >                           @TOP_BUILDDIR@/include/libcamera/ipa/vimc_*.h
> >
> >  EXCLUDE_SYMBOLS        = libcamera::BoundMethodArgs \
> > diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build
> > index 3ee3ada303c0..9ac15546102f 100644
> > --- a/include/libcamera/ipa/meson.build
> > +++ b/include/libcamera/ipa/meson.build
> > @@ -66,6 +66,7 @@ pipeline_ipa_mojom_mapping = {
> >      'ipu3': 'ipu3.mojom',
> >      'mali-c55': 'mali-c55.mojom',
> >      'rkisp1': 'rkisp1.mojom',
> > +    'rcar-gen4': 'rppx1.mojom',
> >      'rpi/pisp': 'raspberrypi.mojom',
> >      'rpi/vc4': 'raspberrypi.mojom',
> >      'simple': 'soft.mojom',
> > diff --git a/include/libcamera/ipa/rppx1.mojom b/include/libcamera/ipa/rppx1.mojom
> > new file mode 100644
> > index 000000000000..4abbebb24f85
> > --- /dev/null
> > +++ b/include/libcamera/ipa/rppx1.mojom
> > @@ -0,0 +1,41 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +
> > +/*
> > + * \todo Document the interface and remove the related EXCLUDE_PATTERNS entry.
> > + */
> > +
> > +module ipa.rppx1;
> > +
> > +import "include/libcamera/ipa/core.mojom";
> > +
> > +struct IPAConfigInfo {
> > +	libcamera.IPACameraSensorInfo sensorInfo;
> > +	libcamera.ControlInfoMap sensorControls;
> > +};
> > +
> > +interface IPARppX1Interface {
> > +	init(libcamera.IPASettings settings,
> > +	     libcamera.IPACameraSensorInfo sensorInfo,
> > +	     libcamera.ControlInfoMap sensorControls)
> > +		=> (int32 ret, libcamera.ControlInfoMap ipaControls);
> > +	start() => (int32 ret);
> > +	stop();
> > +
> > +	configure(IPAConfigInfo configInfo,
> > +		  map<uint32, libcamera.IPAStream> streamConfig)
> > +		=> (int32 ret, libcamera.ControlInfoMap ipaControls);
> > +
> > +	mapBuffers(array<libcamera.IPABuffer> buffers);
> > +	unmapBuffers(array<uint32> ids);
> > +
> > +	[async] queueRequest(uint32 frame, libcamera.ControlList reqControls);
> > +	[async] computeParams(uint32 frame, uint32 bufferId);
> > +	[async] processStats(uint32 frame, uint32 bufferId,
> > +			     libcamera.ControlList sensorControls);
> > +};
> > +
> > +interface IPARppX1EventInterface {
> > +	paramsComputed(uint32 frame, uint32 bytesused);
> > +	setSensorControls(uint32 frame, libcamera.ControlList sensorControls);
> > +	metadataReady(uint32 frame, libcamera.ControlList metadata);
> > +};
> > diff --git a/meson_options.txt b/meson_options.txt
> > index 20baacc4fc65..0aa3aef24edd 100644
> > --- a/meson_options.txt
> > +++ b/meson_options.txt
> > @@ -48,8 +48,8 @@ option('gstreamer',
> >
> >  option('ipas',
> >          type : 'array',
> > -        choices : ['ipu3', 'mali-c55', 'rkisp1', 'rpi/pisp', 'rpi/vc4', 'simple',
> > -                   'vimc'],
> > +        choices : ['ipu3', 'mali-c55', 'rkisp1', 'rppx1', 'rpi/pisp', 'rpi/vc4',
> > +                   'simple', 'vimc'],
>
> nit: rppx1 should be sorted after rpi/*, no?

Only if you know the alphabet, something I don't seem to really grasp

Thanks for the catch

>
> With this considered,
>
> Reviewed-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
>
> >          description : 'Select which IPA modules to build')
> >
> >  option('lc-compliance',
> > diff --git a/src/ipa/meson.build b/src/ipa/meson.build
> > index c583c7efdd35..411d043021f8 100644
> > --- a/src/ipa/meson.build
> > +++ b/src/ipa/meson.build
> > @@ -27,6 +27,7 @@ ipa_sign = files('ipa-sign.sh')
> >  supported_ipas = {
> >      'ipu3':       'ipu3',
> >      'mali-c55':   'mali-c55',
> > +    'rcar-gen4':  'rppx1',
> >      'rkisp1':     'rkisp1',
> >      'rpi/pisp':   'rpi/pisp',
> >      'rpi/vc4':    'rpi/vc4',
> > diff --git a/src/ipa/rppx1/ipa_context.cpp b/src/ipa/rppx1/ipa_context.cpp
> > new file mode 100644
> > index 000000000000..5ac60dfc7bf6
> > --- /dev/null
> > +++ b/src/ipa/rppx1/ipa_context.cpp
> > @@ -0,0 +1,63 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2026 Renesas Electronics Corp.
> > + * Copyright (C) 2026 Ideas on Board Oy
> > + * Copyright (C) 2026 Ragnatech AB
> > + *
> > + * RPP-X1 IPA Context
> > + */
> > +
> > +#include "ipa_context.h"
> > +
> > +/**
> > + * \file ipa_context.h
> > + * \brief Context and state information shared between the algorithms
> > + */
> > +
> > +namespace libcamera::ipa::rppx1 {
> > +
> > +/**
> > + * \struct IPASessionConfiguration
> > + * \brief Session configuration for the IPA module
> > + */
> > +
> > +/**
> > + * \struct IPAActiveState
> > + * \brief Active state for algorithms
> > + */
> > +
> > +/**
> > + * \struct IPAFrameContext
> > + * \brief Per-frame context for algorithms
> > + */
> > +
> > +/**
> > + * \struct IPAContext
> > + * \brief Global IPA context data shared between all algorithms
> > + *
> > + * \var IPAContext::sensorInfo
> > + * \brief The IPA session sensorInfo, immutable during the session
> > + *
> > + * \var IPAContext::configuration
> > + * \brief The IPA session configuration, immutable during the session
> > + *
> > + * \var IPAContext::activeState
> > + * \brief The IPA active state, storing the latest state for all algorithms
> > + *
> > + * \var IPAContext::frameContexts
> > + * \brief Ring buffer of per-frame contexts
> > + *
> > + * \var IPAContext::ctrlMap
> > + * \brief The IPA map of controls
> > + *
> > + * \var IPAContext::camHelper
> > + * \brief The IPA camera helper
> > + */
> > +
> > +/**
> > + * \fn IPAContext::IPAContext
> > + * \brief Construct the IPA context
> > + * \param[in] frameContextSize Size of the frame context queue
> > + */
> > +
> > +} /* namespace libcamera::ipa::rppx1 */
> > diff --git a/src/ipa/rppx1/ipa_context.h b/src/ipa/rppx1/ipa_context.h
> > new file mode 100644
> > index 000000000000..f268a35081bc
> > --- /dev/null
> > +++ b/src/ipa/rppx1/ipa_context.h
> > @@ -0,0 +1,55 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2026 Renesas Electronics Corp.
> > + * Copyright (C) 2026 Ideas on Board Oy
> > + * Copyright (C) 2026 Ragnatech AB
> > + *
> > + * RPP-X1 IPA Context
> > + */
> > +
> > +#pragma once
> > +
> > +#include <memory>
> > +
> > +#include <linux/media/dreamchip/rppx1-config.h>
> > +
> > +#include <libcamera/controls.h>
> > +
> > +#include <libcamera/ipa/core_ipa_interface.h>
> > +
> > +#include <libipa/camera_sensor_helper.h>
> > +#include <libipa/fc_queue.h>
> > +
> > +namespace libcamera {
> > +
> > +namespace ipa::rppx1 {
> > +
> > +struct IPASessionConfiguration {
> > +};
> > +
> > +struct IPAActiveState {
> > +};
> > +
> > +struct IPAFrameContext : public FrameContext {
> > +};
> > +
> > +struct IPAContext {
> > +	IPAContext(unsigned int frameContextSize)
> > +		: frameContexts(frameContextSize)
> > +	{
> > +	}
> > +
> > +	IPACameraSensorInfo sensorInfo;
> > +	IPASessionConfiguration configuration;
> > +	IPAActiveState activeState;
> > +
> > +	FCQueue<IPAFrameContext> frameContexts;
> > +
> > +	ControlInfoMap::Map ctrlMap;
> > +
> > +	std::unique_ptr<CameraSensorHelper> camHelper;
> > +};
> > +
> > +} /* namespace ipa::rppx1 */
> > +
> > +} /* namespace libcamera*/
> > diff --git a/src/ipa/rppx1/meson.build b/src/ipa/rppx1/meson.build
> > new file mode 100644
> > index 000000000000..8034fe24d241
> > --- /dev/null
> > +++ b/src/ipa/rppx1/meson.build
> > @@ -0,0 +1,26 @@
> > +# SPDX-License-Identifier: CC0-1.0
> > +
> > +ipa_name = 'ipa_rppx1'
> > +
> > +rppx1_ipa_sources = files([
> > +    'ipa_context.cpp',
> > +    'rppx1.cpp',
> > +])
> > +
> > +mod = shared_module(ipa_name, rppx1_ipa_sources,
> > +                    name_prefix : '',
> > +                    include_directories : [ipa_includes],
> > +                    dependencies : [libcamera_private, libipa_dep],
> > +                    install : true,
> > +                    install_dir : ipa_install_dir)
> > +
> > +if ipa_sign_module
> > +    custom_target(ipa_name + '.so.sign',
> > +                  input : mod,
> > +                  output : ipa_name + '.so.sign',
> > +                  command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'],
> > +                  install : false,
> > +                  build_by_default : true)
> > +endif
> > +
> > +ipa_names += ipa_name
> > diff --git a/src/ipa/rppx1/module.h b/src/ipa/rppx1/module.h
> > new file mode 100644
> > index 000000000000..0bacff64de8f
> > --- /dev/null
> > +++ b/src/ipa/rppx1/module.h
> > @@ -0,0 +1,27 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2026 Renesas Electronics Corp.
> > + * Copyright (C) 2026 Ideas on Board Oy
> > + * Copyright (C) 2026 Ragnatech AB
> > + *
> > + * RPP-X1 IPA Module
> > + */
> > +
> > +#pragma once
> > +
> > +#include <libipa/module.h>
> > +
> > +#include "ipa_context.h"
> > +#include "params.h"
> > +#include "stats.h"
> > +
> > +namespace libcamera {
> > +
> > +namespace ipa::rppx1 {
> > +
> > +using Module = ipa::Module<IPAContext, IPAFrameContext, IPACameraSensorInfo,
> > +			   RppX1Params, RppX1Stats>;
> > +
> > +} /* namespace ipa::rppx1 */
> > +
> > +} /* namespace libcamera*/
> > diff --git a/src/ipa/rppx1/rppx1.cpp b/src/ipa/rppx1/rppx1.cpp
> > new file mode 100644
> > index 000000000000..20ca2d292704
> > --- /dev/null
> > +++ b/src/ipa/rppx1/rppx1.cpp
> > @@ -0,0 +1,280 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2026 Renesas Electronics Corp.
> > + * Copyright (C) 2026 Ideas on Board Oy
> > + * Copyright (C) 2026 Ragnatech AB
> > + *
> > + * RPP-X1 Image Processing Algorithms
> > + */
> > +
> > +#include <algorithm>
> > +#include <stdint.h>
> > +#include <string.h>
> > +
> > +#include <libcamera/base/file.h>
> > +#include <libcamera/base/log.h>
> > +
> > +#include <libcamera/controls.h>
> > +#include <libcamera/framebuffer.h>
> > +
> > +#include <libcamera/ipa/ipa_interface.h>
> > +#include <libcamera/ipa/ipa_module_info.h>
> > +#include <libcamera/ipa/rppx1_ipa_interface.h>
> > +
> > +#include "libcamera/internal/formats.h"
> > +#include "libcamera/internal/mapped_framebuffer.h"
> > +#include "libcamera/internal/yaml_parser.h"
> > +
> > +#include "algorithms/algorithm.h"
> > +
> > +#include "ipa_context.h"
> > +#include "params.h"
> > +#include "stats.h"
> > +
> > +namespace libcamera {
> > +
> > +LOG_DEFINE_CATEGORY(IPARppX1)
> > +
> > +namespace ipa::rppx1 {
> > +
> > +/* Maximum number of frame contexts to be held */
> > +static constexpr uint32_t kMaxFrameContexts = 16;
> > +
> > +class IPARppX1 : public IPARppX1Interface, public Module
> > +{
> > +public:
> > +	IPARppX1();
> > +
> > +	int init(const IPASettings &settings,
> > +		 const IPACameraSensorInfo &sensorInfo,
> > +		 const ControlInfoMap &sensorControls,
> > +		 ControlInfoMap *ipaControls) override;
> > +	int start() override;
> > +	void stop() override;
> > +
> > +	int configure(const IPAConfigInfo &ipaConfig,
> > +		      const std::map<uint32_t, IPAStream> &streamConfig,
> > +		      ControlInfoMap *ipaControls) override;
> > +	void mapBuffers(const std::vector<IPABuffer> &buffers) override;
> > +	void unmapBuffers(const std::vector<unsigned int> &ids) override;
> > +
> > +	void queueRequest(const uint32_t frame, const ControlList &controls) override;
> > +	void computeParams(const uint32_t frame, const uint32_t bufferId) override;
> > +	void processStats(const uint32_t frame, const uint32_t bufferId,
> > +			  const ControlList &sensorControls) override;
> > +
> > +protected:
> > +	std::string logPrefix() const override;
> > +
> > +private:
> > +	void updateControls(ControlInfoMap *ipaControls);
> > +
> > +	std::map<unsigned int, FrameBuffer> buffers_;
> > +	std::map<unsigned int, MappedFrameBuffer> mappedBuffers_;
> > +
> > +	/* Local parameter storage */
> > +	struct IPAContext context_;
> > +};
> > +
> > +IPARppX1::IPARppX1()
> > +	: context_(kMaxFrameContexts)
> > +{
> > +}
> > +
> > +std::string IPARppX1::logPrefix() const
> > +{
> > +	return "rppx1";
> > +}
> > +
> > +void IPARppX1::updateControls(ControlInfoMap *ipaControls)
> > +{
> > +	ControlInfoMap::Map ctrlMap;
> > +
> > +	ctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end());
> > +	*ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls);
> > +}
> > +
> > +int IPARppX1::init(const IPASettings &settings,
> > +		   const IPACameraSensorInfo &sensorInfo,
> > +		   [[maybe_unused]] const ControlInfoMap &sensorControls,
> > +		   [[maybe_unused]] ControlInfoMap *ipaControls)
> > +{
> > +	context_.sensorInfo = sensorInfo;
> > +
> > +	context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel);
> > +	if (!context_.camHelper) {
> > +		LOG(IPARppX1, Error)
> > +			<< "Failed to create camera sensor helper for "
> > +			<< settings.sensorModel;
> > +		return -ENODEV;
> > +	}
> > +
> > +	/* Load the tuning data file. */
> > +	File file(settings.configurationFile);
> > +	if (!file.open(File::OpenModeFlag::ReadOnly)) {
> > +		int ret = file.error();
> > +		LOG(IPARppX1, Error)
> > +			<< "Failed to open configuration file "
> > +			<< settings.configurationFile << ": " << strerror(-ret);
> > +		return ret;
> > +	}
> > +
> > +	std::unique_ptr<libcamera::ValueNode> data = YamlParser::parse(file);
> > +	if (!data)
> > +		return -EINVAL;
> > +
> > +	unsigned int version = (*data)["version"].get<uint32_t>(0);
> > +	if (version != 1) {
> > +		LOG(IPARppX1, Error)
> > +			<< "Invalid tuning file version " << version;
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (!data->contains("algorithms")) {
> > +		LOG(IPARppX1, Error)
> > +			<< "Tuning file doesn't contain any algorithm";
> > +		return -EINVAL;
> > +	}
> > +
> > +	int ret = createAlgorithms(context_, (*data)["algorithms"]);
> > +	if (ret)
> > +		return ret;
> > +
> > +	/* Initialize controls. */
> > +	updateControls(ipaControls);
> > +
> > +	return 0;
> > +}
> > +
> > +int IPARppX1::start()
> > +{
> > +	/* \todo Properly handle startup controls. */
> > +	return 0;
> > +}
> > +
> > +void IPARppX1::stop()
> > +{
> > +	context_.frameContexts.clear();
> > +}
> > +
> > +int IPARppX1::configure(const IPAConfigInfo &ipaConfig,
> > +			[[maybe_unused]] const std::map<uint32_t, IPAStream> &streamConfig,
> > +			[[maybe_unused]] ControlInfoMap *ipaControls)
> > +{
> > +	/* Clear the IPA context before the streaming session. */
> > +	context_.configuration = {};
> > +	context_.activeState = {};
> > +	context_.frameContexts.clear();
> > +
> > +	const IPACameraSensorInfo &info = ipaConfig.sensorInfo;
> > +
> > +	/* Update the camera controls using the new sensor settings. */
> > +	updateControls(ipaControls);
> > +
> > +	for (auto const &a : algorithms()) {
> > +		Algorithm *algo = static_cast<Algorithm *>(a.get());
> > +
> > +		int ret = algo->configure(context_, info);
> > +		if (ret)
> > +			return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +void IPARppX1::mapBuffers(const std::vector<IPABuffer> &buffers)
> > +{
> > +	for (const IPABuffer &buffer : buffers) {
> > +		auto elem = buffers_.emplace(std::piecewise_construct,
> > +					     std::forward_as_tuple(buffer.id),
> > +					     std::forward_as_tuple(buffer.planes));
> > +		const FrameBuffer &fb = elem.first->second;
> > +
> > +		MappedFrameBuffer mappedBuffer(&fb, MappedFrameBuffer::MapFlag::ReadWrite);
> > +		if (!mappedBuffer.isValid()) {
> > +			LOG(IPARppX1, Fatal) << "Failed to mmap buffer: "
> > +					     << strerror(mappedBuffer.error());
> > +		}
> > +
> > +		mappedBuffers_.emplace(buffer.id, std::move(mappedBuffer));
> > +	}
> > +}
> > +
> > +void IPARppX1::unmapBuffers(const std::vector<unsigned int> &ids)
> > +{
> > +	for (unsigned int id : ids) {
> > +		const auto fb = buffers_.find(id);
> > +		if (fb == buffers_.end())
> > +			continue;
> > +
> > +		mappedBuffers_.erase(id);
> > +		buffers_.erase(id);
> > +	}
> > +}
> > +
> > +void IPARppX1::queueRequest(const uint32_t frame, const ControlList &controls)
> > +{
> > +	IPAFrameContext &frameContext = context_.frameContexts.alloc(frame);
> > +
> > +	for (auto const &a : algorithms()) {
> > +		Algorithm *algo = static_cast<Algorithm *>(a.get());
> > +		if (algo->disabled_)
> > +			continue;
> > +
> > +		algo->queueRequest(context_, frame, frameContext, controls);
> > +	}
> > +}
> > +
> > +void IPARppX1::computeParams(const uint32_t frame, const uint32_t bufferId)
> > +{
> > +	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
> > +
> > +	RppX1Params params(mappedBuffers_.at(bufferId).planes()[0]);
> > +
> > +	for (auto const &algo : algorithms())
> > +		algo->prepare(context_, frame, frameContext, &params);
> > +
> > +	paramsComputed.emit(frame, params.bytesused());
> > +}
> > +
> > +void IPARppX1::processStats(const uint32_t frame, const uint32_t bufferId,
> > +			    [[maybe_unused]] const ControlList &sensorControls)
> > +{
> > +	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
> > +
> > +	ControlList metadata(controls::controls);
> > +
> > +	auto stats = RppX1Stats(mappedBuffers_.at(bufferId).planes()[0]);
> > +	if (!stats)
> > +		return;
> > +
> > +	for (auto const &a : algorithms()) {
> > +		Algorithm *algo = static_cast<Algorithm *>(a.get());
> > +		if (algo->disabled_)
> > +			continue;
> > +		algo->process(context_, frame, frameContext, &stats, metadata);
> > +	}
> > +
> > +	metadataReady.emit(frame, metadata);
> > +}
> > +
> > +} /* namespace ipa::rppx1 */
> > +
> > +/*
> > + * External IPA module interface
> > + */
> > +
> > +extern "C" {
> > +const struct IPAModuleInfo ipaModuleInfo = {
> > +	IPA_MODULE_API_VERSION,
> > +	1,
> > +	"rppx1",
> > +};
> > +
> > +IPAInterface *ipaCreate()
> > +{
> > +	return new ipa::rppx1::IPARppX1();
> > +}
> > +}
> > +
> > +} /* namespace libcamera */
> >
> > --
> > 2.54.0
> >
>
> --
> Kind Regards,
> Niklas Söderlund

Patch
diff mbox series

diff --git a/Documentation/Doxyfile-common.in b/Documentation/Doxyfile-common.in
index f11ec593d5ae..131fdcc6083f 100644
--- a/Documentation/Doxyfile-common.in
+++ b/Documentation/Doxyfile-common.in
@@ -37,6 +37,7 @@  EXCLUDE_PATTERNS       = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \
                          @TOP_BUILDDIR@/include/libcamera/ipa/mali-c55_*.h \
                          @TOP_BUILDDIR@/include/libcamera/ipa/raspberrypi_*.h \
                          @TOP_BUILDDIR@/include/libcamera/ipa/rkisp1_*.h \
+                         @TOP_BUILDDIR@/include/libcamera/ipa/rppx1_*.h \
                          @TOP_BUILDDIR@/include/libcamera/ipa/vimc_*.h
 
 EXCLUDE_SYMBOLS        = libcamera::BoundMethodArgs \
diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build
index 3ee3ada303c0..9ac15546102f 100644
--- a/include/libcamera/ipa/meson.build
+++ b/include/libcamera/ipa/meson.build
@@ -66,6 +66,7 @@  pipeline_ipa_mojom_mapping = {
     'ipu3': 'ipu3.mojom',
     'mali-c55': 'mali-c55.mojom',
     'rkisp1': 'rkisp1.mojom',
+    'rcar-gen4': 'rppx1.mojom',
     'rpi/pisp': 'raspberrypi.mojom',
     'rpi/vc4': 'raspberrypi.mojom',
     'simple': 'soft.mojom',
diff --git a/include/libcamera/ipa/rppx1.mojom b/include/libcamera/ipa/rppx1.mojom
new file mode 100644
index 000000000000..4abbebb24f85
--- /dev/null
+++ b/include/libcamera/ipa/rppx1.mojom
@@ -0,0 +1,41 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+/*
+ * \todo Document the interface and remove the related EXCLUDE_PATTERNS entry.
+ */
+
+module ipa.rppx1;
+
+import "include/libcamera/ipa/core.mojom";
+
+struct IPAConfigInfo {
+	libcamera.IPACameraSensorInfo sensorInfo;
+	libcamera.ControlInfoMap sensorControls;
+};
+
+interface IPARppX1Interface {
+	init(libcamera.IPASettings settings,
+	     libcamera.IPACameraSensorInfo sensorInfo,
+	     libcamera.ControlInfoMap sensorControls)
+		=> (int32 ret, libcamera.ControlInfoMap ipaControls);
+	start() => (int32 ret);
+	stop();
+
+	configure(IPAConfigInfo configInfo,
+		  map<uint32, libcamera.IPAStream> streamConfig)
+		=> (int32 ret, libcamera.ControlInfoMap ipaControls);
+
+	mapBuffers(array<libcamera.IPABuffer> buffers);
+	unmapBuffers(array<uint32> ids);
+
+	[async] queueRequest(uint32 frame, libcamera.ControlList reqControls);
+	[async] computeParams(uint32 frame, uint32 bufferId);
+	[async] processStats(uint32 frame, uint32 bufferId,
+			     libcamera.ControlList sensorControls);
+};
+
+interface IPARppX1EventInterface {
+	paramsComputed(uint32 frame, uint32 bytesused);
+	setSensorControls(uint32 frame, libcamera.ControlList sensorControls);
+	metadataReady(uint32 frame, libcamera.ControlList metadata);
+};
diff --git a/meson_options.txt b/meson_options.txt
index 20baacc4fc65..0aa3aef24edd 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -48,8 +48,8 @@  option('gstreamer',
 
 option('ipas',
         type : 'array',
-        choices : ['ipu3', 'mali-c55', 'rkisp1', 'rpi/pisp', 'rpi/vc4', 'simple',
-                   'vimc'],
+        choices : ['ipu3', 'mali-c55', 'rkisp1', 'rppx1', 'rpi/pisp', 'rpi/vc4',
+                   'simple', 'vimc'],
         description : 'Select which IPA modules to build')
 
 option('lc-compliance',
diff --git a/src/ipa/meson.build b/src/ipa/meson.build
index c583c7efdd35..411d043021f8 100644
--- a/src/ipa/meson.build
+++ b/src/ipa/meson.build
@@ -27,6 +27,7 @@  ipa_sign = files('ipa-sign.sh')
 supported_ipas = {
     'ipu3':       'ipu3',
     'mali-c55':   'mali-c55',
+    'rcar-gen4':  'rppx1',
     'rkisp1':     'rkisp1',
     'rpi/pisp':   'rpi/pisp',
     'rpi/vc4':    'rpi/vc4',
diff --git a/src/ipa/rppx1/ipa_context.cpp b/src/ipa/rppx1/ipa_context.cpp
new file mode 100644
index 000000000000..5ac60dfc7bf6
--- /dev/null
+++ b/src/ipa/rppx1/ipa_context.cpp
@@ -0,0 +1,63 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026 Renesas Electronics Corp.
+ * Copyright (C) 2026 Ideas on Board Oy
+ * Copyright (C) 2026 Ragnatech AB
+ *
+ * RPP-X1 IPA Context
+ */
+
+#include "ipa_context.h"
+
+/**
+ * \file ipa_context.h
+ * \brief Context and state information shared between the algorithms
+ */
+
+namespace libcamera::ipa::rppx1 {
+
+/**
+ * \struct IPASessionConfiguration
+ * \brief Session configuration for the IPA module
+ */
+
+/**
+ * \struct IPAActiveState
+ * \brief Active state for algorithms
+ */
+
+/**
+ * \struct IPAFrameContext
+ * \brief Per-frame context for algorithms
+ */
+
+/**
+ * \struct IPAContext
+ * \brief Global IPA context data shared between all algorithms
+ *
+ * \var IPAContext::sensorInfo
+ * \brief The IPA session sensorInfo, immutable during the session
+ *
+ * \var IPAContext::configuration
+ * \brief The IPA session configuration, immutable during the session
+ *
+ * \var IPAContext::activeState
+ * \brief The IPA active state, storing the latest state for all algorithms
+ *
+ * \var IPAContext::frameContexts
+ * \brief Ring buffer of per-frame contexts
+ *
+ * \var IPAContext::ctrlMap
+ * \brief The IPA map of controls
+ *
+ * \var IPAContext::camHelper
+ * \brief The IPA camera helper
+ */
+
+/**
+ * \fn IPAContext::IPAContext
+ * \brief Construct the IPA context
+ * \param[in] frameContextSize Size of the frame context queue
+ */
+
+} /* namespace libcamera::ipa::rppx1 */
diff --git a/src/ipa/rppx1/ipa_context.h b/src/ipa/rppx1/ipa_context.h
new file mode 100644
index 000000000000..f268a35081bc
--- /dev/null
+++ b/src/ipa/rppx1/ipa_context.h
@@ -0,0 +1,55 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026 Renesas Electronics Corp.
+ * Copyright (C) 2026 Ideas on Board Oy
+ * Copyright (C) 2026 Ragnatech AB
+ *
+ * RPP-X1 IPA Context
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <linux/media/dreamchip/rppx1-config.h>
+
+#include <libcamera/controls.h>
+
+#include <libcamera/ipa/core_ipa_interface.h>
+
+#include <libipa/camera_sensor_helper.h>
+#include <libipa/fc_queue.h>
+
+namespace libcamera {
+
+namespace ipa::rppx1 {
+
+struct IPASessionConfiguration {
+};
+
+struct IPAActiveState {
+};
+
+struct IPAFrameContext : public FrameContext {
+};
+
+struct IPAContext {
+	IPAContext(unsigned int frameContextSize)
+		: frameContexts(frameContextSize)
+	{
+	}
+
+	IPACameraSensorInfo sensorInfo;
+	IPASessionConfiguration configuration;
+	IPAActiveState activeState;
+
+	FCQueue<IPAFrameContext> frameContexts;
+
+	ControlInfoMap::Map ctrlMap;
+
+	std::unique_ptr<CameraSensorHelper> camHelper;
+};
+
+} /* namespace ipa::rppx1 */
+
+} /* namespace libcamera*/
diff --git a/src/ipa/rppx1/meson.build b/src/ipa/rppx1/meson.build
new file mode 100644
index 000000000000..8034fe24d241
--- /dev/null
+++ b/src/ipa/rppx1/meson.build
@@ -0,0 +1,26 @@ 
+# SPDX-License-Identifier: CC0-1.0
+
+ipa_name = 'ipa_rppx1'
+
+rppx1_ipa_sources = files([
+    'ipa_context.cpp',
+    'rppx1.cpp',
+])
+
+mod = shared_module(ipa_name, rppx1_ipa_sources,
+                    name_prefix : '',
+                    include_directories : [ipa_includes],
+                    dependencies : [libcamera_private, libipa_dep],
+                    install : true,
+                    install_dir : ipa_install_dir)
+
+if ipa_sign_module
+    custom_target(ipa_name + '.so.sign',
+                  input : mod,
+                  output : ipa_name + '.so.sign',
+                  command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'],
+                  install : false,
+                  build_by_default : true)
+endif
+
+ipa_names += ipa_name
diff --git a/src/ipa/rppx1/module.h b/src/ipa/rppx1/module.h
new file mode 100644
index 000000000000..0bacff64de8f
--- /dev/null
+++ b/src/ipa/rppx1/module.h
@@ -0,0 +1,27 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026 Renesas Electronics Corp.
+ * Copyright (C) 2026 Ideas on Board Oy
+ * Copyright (C) 2026 Ragnatech AB
+ *
+ * RPP-X1 IPA Module
+ */
+
+#pragma once
+
+#include <libipa/module.h>
+
+#include "ipa_context.h"
+#include "params.h"
+#include "stats.h"
+
+namespace libcamera {
+
+namespace ipa::rppx1 {
+
+using Module = ipa::Module<IPAContext, IPAFrameContext, IPACameraSensorInfo,
+			   RppX1Params, RppX1Stats>;
+
+} /* namespace ipa::rppx1 */
+
+} /* namespace libcamera*/
diff --git a/src/ipa/rppx1/rppx1.cpp b/src/ipa/rppx1/rppx1.cpp
new file mode 100644
index 000000000000..20ca2d292704
--- /dev/null
+++ b/src/ipa/rppx1/rppx1.cpp
@@ -0,0 +1,280 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2026 Renesas Electronics Corp.
+ * Copyright (C) 2026 Ideas on Board Oy
+ * Copyright (C) 2026 Ragnatech AB
+ *
+ * RPP-X1 Image Processing Algorithms
+ */
+
+#include <algorithm>
+#include <stdint.h>
+#include <string.h>
+
+#include <libcamera/base/file.h>
+#include <libcamera/base/log.h>
+
+#include <libcamera/controls.h>
+#include <libcamera/framebuffer.h>
+
+#include <libcamera/ipa/ipa_interface.h>
+#include <libcamera/ipa/ipa_module_info.h>
+#include <libcamera/ipa/rppx1_ipa_interface.h>
+
+#include "libcamera/internal/formats.h"
+#include "libcamera/internal/mapped_framebuffer.h"
+#include "libcamera/internal/yaml_parser.h"
+
+#include "algorithms/algorithm.h"
+
+#include "ipa_context.h"
+#include "params.h"
+#include "stats.h"
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(IPARppX1)
+
+namespace ipa::rppx1 {
+
+/* Maximum number of frame contexts to be held */
+static constexpr uint32_t kMaxFrameContexts = 16;
+
+class IPARppX1 : public IPARppX1Interface, public Module
+{
+public:
+	IPARppX1();
+
+	int init(const IPASettings &settings,
+		 const IPACameraSensorInfo &sensorInfo,
+		 const ControlInfoMap &sensorControls,
+		 ControlInfoMap *ipaControls) override;
+	int start() override;
+	void stop() override;
+
+	int configure(const IPAConfigInfo &ipaConfig,
+		      const std::map<uint32_t, IPAStream> &streamConfig,
+		      ControlInfoMap *ipaControls) override;
+	void mapBuffers(const std::vector<IPABuffer> &buffers) override;
+	void unmapBuffers(const std::vector<unsigned int> &ids) override;
+
+	void queueRequest(const uint32_t frame, const ControlList &controls) override;
+	void computeParams(const uint32_t frame, const uint32_t bufferId) override;
+	void processStats(const uint32_t frame, const uint32_t bufferId,
+			  const ControlList &sensorControls) override;
+
+protected:
+	std::string logPrefix() const override;
+
+private:
+	void updateControls(ControlInfoMap *ipaControls);
+
+	std::map<unsigned int, FrameBuffer> buffers_;
+	std::map<unsigned int, MappedFrameBuffer> mappedBuffers_;
+
+	/* Local parameter storage */
+	struct IPAContext context_;
+};
+
+IPARppX1::IPARppX1()
+	: context_(kMaxFrameContexts)
+{
+}
+
+std::string IPARppX1::logPrefix() const
+{
+	return "rppx1";
+}
+
+void IPARppX1::updateControls(ControlInfoMap *ipaControls)
+{
+	ControlInfoMap::Map ctrlMap;
+
+	ctrlMap.insert(context_.ctrlMap.begin(), context_.ctrlMap.end());
+	*ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls);
+}
+
+int IPARppX1::init(const IPASettings &settings,
+		   const IPACameraSensorInfo &sensorInfo,
+		   [[maybe_unused]] const ControlInfoMap &sensorControls,
+		   [[maybe_unused]] ControlInfoMap *ipaControls)
+{
+	context_.sensorInfo = sensorInfo;
+
+	context_.camHelper = CameraSensorHelperFactoryBase::create(settings.sensorModel);
+	if (!context_.camHelper) {
+		LOG(IPARppX1, Error)
+			<< "Failed to create camera sensor helper for "
+			<< settings.sensorModel;
+		return -ENODEV;
+	}
+
+	/* Load the tuning data file. */
+	File file(settings.configurationFile);
+	if (!file.open(File::OpenModeFlag::ReadOnly)) {
+		int ret = file.error();
+		LOG(IPARppX1, Error)
+			<< "Failed to open configuration file "
+			<< settings.configurationFile << ": " << strerror(-ret);
+		return ret;
+	}
+
+	std::unique_ptr<libcamera::ValueNode> data = YamlParser::parse(file);
+	if (!data)
+		return -EINVAL;
+
+	unsigned int version = (*data)["version"].get<uint32_t>(0);
+	if (version != 1) {
+		LOG(IPARppX1, Error)
+			<< "Invalid tuning file version " << version;
+		return -EINVAL;
+	}
+
+	if (!data->contains("algorithms")) {
+		LOG(IPARppX1, Error)
+			<< "Tuning file doesn't contain any algorithm";
+		return -EINVAL;
+	}
+
+	int ret = createAlgorithms(context_, (*data)["algorithms"]);
+	if (ret)
+		return ret;
+
+	/* Initialize controls. */
+	updateControls(ipaControls);
+
+	return 0;
+}
+
+int IPARppX1::start()
+{
+	/* \todo Properly handle startup controls. */
+	return 0;
+}
+
+void IPARppX1::stop()
+{
+	context_.frameContexts.clear();
+}
+
+int IPARppX1::configure(const IPAConfigInfo &ipaConfig,
+			[[maybe_unused]] const std::map<uint32_t, IPAStream> &streamConfig,
+			[[maybe_unused]] ControlInfoMap *ipaControls)
+{
+	/* Clear the IPA context before the streaming session. */
+	context_.configuration = {};
+	context_.activeState = {};
+	context_.frameContexts.clear();
+
+	const IPACameraSensorInfo &info = ipaConfig.sensorInfo;
+
+	/* Update the camera controls using the new sensor settings. */
+	updateControls(ipaControls);
+
+	for (auto const &a : algorithms()) {
+		Algorithm *algo = static_cast<Algorithm *>(a.get());
+
+		int ret = algo->configure(context_, info);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+void IPARppX1::mapBuffers(const std::vector<IPABuffer> &buffers)
+{
+	for (const IPABuffer &buffer : buffers) {
+		auto elem = buffers_.emplace(std::piecewise_construct,
+					     std::forward_as_tuple(buffer.id),
+					     std::forward_as_tuple(buffer.planes));
+		const FrameBuffer &fb = elem.first->second;
+
+		MappedFrameBuffer mappedBuffer(&fb, MappedFrameBuffer::MapFlag::ReadWrite);
+		if (!mappedBuffer.isValid()) {
+			LOG(IPARppX1, Fatal) << "Failed to mmap buffer: "
+					     << strerror(mappedBuffer.error());
+		}
+
+		mappedBuffers_.emplace(buffer.id, std::move(mappedBuffer));
+	}
+}
+
+void IPARppX1::unmapBuffers(const std::vector<unsigned int> &ids)
+{
+	for (unsigned int id : ids) {
+		const auto fb = buffers_.find(id);
+		if (fb == buffers_.end())
+			continue;
+
+		mappedBuffers_.erase(id);
+		buffers_.erase(id);
+	}
+}
+
+void IPARppX1::queueRequest(const uint32_t frame, const ControlList &controls)
+{
+	IPAFrameContext &frameContext = context_.frameContexts.alloc(frame);
+
+	for (auto const &a : algorithms()) {
+		Algorithm *algo = static_cast<Algorithm *>(a.get());
+		if (algo->disabled_)
+			continue;
+
+		algo->queueRequest(context_, frame, frameContext, controls);
+	}
+}
+
+void IPARppX1::computeParams(const uint32_t frame, const uint32_t bufferId)
+{
+	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
+
+	RppX1Params params(mappedBuffers_.at(bufferId).planes()[0]);
+
+	for (auto const &algo : algorithms())
+		algo->prepare(context_, frame, frameContext, &params);
+
+	paramsComputed.emit(frame, params.bytesused());
+}
+
+void IPARppX1::processStats(const uint32_t frame, const uint32_t bufferId,
+			    [[maybe_unused]] const ControlList &sensorControls)
+{
+	IPAFrameContext &frameContext = context_.frameContexts.get(frame);
+
+	ControlList metadata(controls::controls);
+
+	auto stats = RppX1Stats(mappedBuffers_.at(bufferId).planes()[0]);
+	if (!stats)
+		return;
+
+	for (auto const &a : algorithms()) {
+		Algorithm *algo = static_cast<Algorithm *>(a.get());
+		if (algo->disabled_)
+			continue;
+		algo->process(context_, frame, frameContext, &stats, metadata);
+	}
+
+	metadataReady.emit(frame, metadata);
+}
+
+} /* namespace ipa::rppx1 */
+
+/*
+ * External IPA module interface
+ */
+
+extern "C" {
+const struct IPAModuleInfo ipaModuleInfo = {
+	IPA_MODULE_API_VERSION,
+	1,
+	"rppx1",
+};
+
+IPAInterface *ipaCreate()
+{
+	return new ipa::rppx1::IPARppX1();
+}
+}
+
+} /* namespace libcamera */