[v3,04/11] ipa: c3-isp: Add C3 ISP IPA module
diff mbox series

Message ID 20250227105733.187611-5-keke.li@amlogic.com
State New
Headers show
Series
  • Add Amlogic C3 ISP pipeline handler and IPA
Related show

Commit Message

Keke Li Feb. 27, 2025, 10:57 a.m. UTC
Add a barebones IPA module for the C3 ISP. In this initial
implementation only basic interfaces are implemented.

Signed-off-by: Keke Li <keke.li@amlogic.com>
---
 meson_options.txt                     |   2 +-
 src/ipa/c3-isp/algorithms/algorithm.h |  28 ++
 src/ipa/c3-isp/algorithms/meson.build |   5 +
 src/ipa/c3-isp/c3-isp.cpp             | 370 ++++++++++++++++++++++++++
 src/ipa/c3-isp/data/meson.build       |   9 +
 src/ipa/c3-isp/ipa_context.cpp        | 101 +++++++
 src/ipa/c3-isp/ipa_context.h          |  87 ++++++
 src/ipa/c3-isp/meson.build            |  32 +++
 src/ipa/c3-isp/module.h               |  28 ++
 src/ipa/c3-isp/params.cpp             | 127 +++++++++
 src/ipa/c3-isp/params.h               | 133 +++++++++
 11 files changed, 921 insertions(+), 1 deletion(-)
 create mode 100644 src/ipa/c3-isp/algorithms/algorithm.h
 create mode 100644 src/ipa/c3-isp/algorithms/meson.build
 create mode 100644 src/ipa/c3-isp/c3-isp.cpp
 create mode 100644 src/ipa/c3-isp/data/meson.build
 create mode 100644 src/ipa/c3-isp/ipa_context.cpp
 create mode 100644 src/ipa/c3-isp/ipa_context.h
 create mode 100644 src/ipa/c3-isp/meson.build
 create mode 100644 src/ipa/c3-isp/module.h
 create mode 100644 src/ipa/c3-isp/params.cpp
 create mode 100644 src/ipa/c3-isp/params.h

Patch
diff mbox series

diff --git a/meson_options.txt b/meson_options.txt
index 998a4463..7c666d34 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -32,7 +32,7 @@  option('gstreamer',
 
 option('ipas',
         type : 'array',
-        choices : ['ipu3', 'mali-c55', 'rkisp1', 'rpi/vc4', 'simple', 'vimc'],
+        choices : ['c3-isp', 'ipu3', 'mali-c55', 'rkisp1', 'rpi/vc4', 'simple', 'vimc'],
         description : 'Select which IPA modules to build')
 
 option('lc-compliance',
diff --git a/src/ipa/c3-isp/algorithms/algorithm.h b/src/ipa/c3-isp/algorithms/algorithm.h
new file mode 100644
index 00000000..68c311d7
--- /dev/null
+++ b/src/ipa/c3-isp/algorithms/algorithm.h
@@ -0,0 +1,28 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Amlogic
+ *
+ * C3ISP control algorithm interface
+ */
+
+#pragma once
+
+#include <libipa/algorithm.h>
+
+#include "module.h"
+
+namespace libcamera {
+
+namespace ipa::c3isp {
+
+class Algorithm : public libcamera::ipa::Algorithm<Module>
+{
+public:
+	Algorithm()
+	{
+	}
+};
+
+} /* namespace ipa::c3isp */
+
+} /* namespace libcamera */
diff --git a/src/ipa/c3-isp/algorithms/meson.build b/src/ipa/c3-isp/algorithms/meson.build
new file mode 100644
index 00000000..1e00af33
--- /dev/null
+++ b/src/ipa/c3-isp/algorithms/meson.build
@@ -0,0 +1,5 @@ 
+# SPDX-License-Identifier: CC0-1.0
+
+c3isp_ipa_algorithms = files([
+
+])
diff --git a/src/ipa/c3-isp/c3-isp.cpp b/src/ipa/c3-isp/c3-isp.cpp
new file mode 100644
index 00000000..abb56d15
--- /dev/null
+++ b/src/ipa/c3-isp/c3-isp.cpp
@@ -0,0 +1,370 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Amlogic Inc.
+ *
+ * c3-isp.cpp - Amlogic Image Processing Algorithms
+ */
+
+#include <algorithm>
+#include <array>
+#include <chrono>
+#include <stdint.h>
+#include <string.h>
+
+#include <linux/c3-isp-config.h>
+#include <linux/v4l2-controls.h>
+
+#include <libcamera/base/file.h>
+#include <libcamera/base/log.h>
+
+#include <libcamera/control_ids.h>
+#include <libcamera/controls.h>
+#include <libcamera/framebuffer.h>
+#include <libcamera/request.h>
+
+#include <libcamera/ipa/c3-isp_ipa_interface.h>
+#include <libcamera/ipa/ipa_interface.h>
+#include <libcamera/ipa/ipa_module_info.h>
+
+#include "libcamera/internal/formats.h"
+#include "libcamera/internal/mapped_framebuffer.h"
+#include "libcamera/internal/yaml_parser.h"
+
+#include "algorithms/algorithm.h"
+#include "libipa/camera_sensor_helper.h"
+
+#include "ipa_context.h"
+#include "params.h"
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(IPAC3ISP)
+
+using namespace std::literals::chrono_literals;
+
+namespace ipa::c3isp {
+
+static constexpr uint32_t kMaxFrameContexts = 16;
+
+class IPAC3ISP : public IPAC3ISPInterface, public Module
+{
+public:
+	IPAC3ISP();
+
+	int init(const IPASettings &settings, const IPAConfigInfo &ipaConfig,
+		 ControlInfoMap *ipaControls) override;
+	int start() override;
+	void stop() override;
+
+	int configure(const IPAConfigInfo &ipaConfig,
+		      ControlInfoMap *ipaControls) override;
+	void mapBuffers(const std::vector<IPABuffer> &buffers, bool readOnly) override;
+	void unmapBuffers(const std::vector<IPABuffer> &buffers) override;
+
+	void queueRequest(const uint32_t request, const ControlList &controls) override;
+	void computeParams(const uint32_t request, const uint32_t bufferId) override;
+	void processStats(const uint32_t request, const uint32_t bufferId,
+			  const ControlList &sensorControls) override;
+
+protected:
+	std::string logPrefix() const override;
+
+private:
+	void updateSessionConfiguration(const IPACameraSensorInfo &info,
+					const ControlInfoMap &sensorControls);
+	void updateControls(const IPACameraSensorInfo &sensorInfo,
+			    const ControlInfoMap &sensorControls,
+			    ControlInfoMap *ipaControls);
+	void setControls();
+
+	std::map<unsigned int, MappedFrameBuffer> buffers_;
+
+	ControlInfoMap sensorControls_;
+
+	/* Interface to the Camera Helper */
+	std::unique_ptr<CameraSensorHelper> camHelper_;
+
+	/* Local parameter storage */
+	struct IPAContext context_;
+};
+
+IPAC3ISP::IPAC3ISP()
+	: context_(kMaxFrameContexts)
+{
+}
+
+std::string IPAC3ISP::logPrefix() const
+{
+	return "c3isp";
+}
+
+int IPAC3ISP::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig,
+		   ControlInfoMap *ipaControls)
+{
+	camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
+	if (!camHelper_) {
+		LOG(IPAC3ISP, Error)
+			<< "Failed to create camera sensor helper for "
+			<< settings.sensorModel;
+		return -ENODEV;
+	}
+
+	File file(settings.configurationFile);
+	if (!file.open(File::OpenModeFlag::ReadOnly)) {
+		int ret = file.error();
+		LOG(IPAC3ISP, Error)
+			<< "Failed to open configuration file "
+			<< settings.configurationFile << ": " << strerror(-ret);
+		return ret;
+	}
+
+	std::unique_ptr<libcamera::YamlObject> data = YamlParser::parse(file);
+	if (!data)
+		return -EINVAL;
+
+	if (!data->contains("algorithms")) {
+		LOG(IPAC3ISP, Error)
+			<< "Tuning file doesn't contain any algorithm";
+		return -EINVAL;
+	}
+
+	int ret = createAlgorithms(context_, (*data)["algorithms"]);
+	if (ret)
+		return ret;
+
+	updateControls(ipaConfig.sensorInfo, ipaConfig.sensorControls, ipaControls);
+
+	return 0;
+}
+
+int IPAC3ISP::start()
+{
+	return 0;
+}
+
+void IPAC3ISP::stop()
+{
+	context_.frameContexts.clear();
+}
+
+void IPAC3ISP::updateSessionConfiguration(const IPACameraSensorInfo &info,
+					  const ControlInfoMap &sensorControls)
+{
+	const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second;
+	int32_t minExposure = v4l2Exposure.min().get<int32_t>();
+	int32_t maxExposure = v4l2Exposure.max().get<int32_t>();
+	int32_t defExposure = v4l2Exposure.def().get<int32_t>();
+
+	const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;
+	int32_t minGain = v4l2Gain.min().get<int32_t>();
+	int32_t maxGain = v4l2Gain.max().get<int32_t>();
+
+	context_.configuration.sensor.lineDuration = info.minLineLength * 1.0s / info.pixelRate;
+	context_.configuration.agc.minShutterSpeed = minExposure * context_.configuration.sensor.lineDuration;
+	context_.configuration.agc.maxShutterSpeed = maxExposure * context_.configuration.sensor.lineDuration;
+	context_.configuration.agc.defaultExposure = defExposure;
+	context_.configuration.agc.minAnalogueGain = camHelper_->gain(minGain);
+	context_.configuration.agc.maxAnalogueGain = camHelper_->gain(maxGain);
+
+	context_.configuration.sensor.size = info.outputSize;
+
+	if (camHelper_->blackLevel().has_value())
+		/*
+		 * The black level from CameraSensorHelper is a 16-bit value.
+		 * The C3 ISP expects 20-bit settings, so we shift it to the
+		 * appropriate width
+		 */
+		context_.configuration.sensor.blackLevel = camHelper_->blackLevel().value() << 4;
+}
+
+void IPAC3ISP::updateControls(const IPACameraSensorInfo &sensorInfo,
+			      const ControlInfoMap &sensorControls,
+			      ControlInfoMap *ipaControls)
+{
+	ControlInfoMap::Map ctrlMap;
+
+	/* Compute the frame duration limits. */
+	const ControlInfo &v4l2HBlank = sensorControls.find(V4L2_CID_HBLANK)->second;
+	uint32_t hblank = v4l2HBlank.def().get<int32_t>();
+	uint32_t lineLength = sensorInfo.outputSize.width + hblank;
+
+	const ControlInfo &v4l2VBlank = sensorControls.find(V4L2_CID_VBLANK)->second;
+	std::array<uint32_t, 3> frameHeights{
+		v4l2VBlank.min().get<int32_t>() + sensorInfo.outputSize.height,
+		v4l2VBlank.max().get<int32_t>() + sensorInfo.outputSize.height,
+		v4l2VBlank.def().get<int32_t>() + sensorInfo.outputSize.height,
+	};
+
+	std::array<int64_t, 3> frameDurations;
+	for (unsigned int i = 0; i < frameHeights.size(); ++i) {
+		uint64_t frameSize = lineLength * frameHeights[i];
+		frameDurations[i] = frameSize / (sensorInfo.pixelRate / 1000000U);
+	}
+
+	ctrlMap[&controls::FrameDurationLimits] = ControlInfo(frameDurations[0],
+							      frameDurations[1],
+							      frameDurations[2]);
+
+	/* Compute the exposure time limits */
+	double lineDuration = context_.configuration.sensor.lineDuration.get<std::micro>();
+	const ControlInfo &v4l2Exposure = sensorControls.find(V4L2_CID_EXPOSURE)->second;
+	int32_t minExposure = v4l2Exposure.min().get<int32_t>() * lineDuration;
+	int32_t maxExposure = v4l2Exposure.max().get<int32_t>() * lineDuration;
+	int32_t defExposure = v4l2Exposure.def().get<int32_t>() * lineDuration;
+	ctrlMap[&controls::ExposureTime] = ControlInfo(minExposure, maxExposure, defExposure);
+
+	/* Compute the analogue gain limits. */
+	const ControlInfo &v4l2Gain = sensorControls.find(V4L2_CID_ANALOGUE_GAIN)->second;
+	float minGain = camHelper_->gain(v4l2Gain.min().get<int32_t>());
+	float maxGain = camHelper_->gain(v4l2Gain.max().get<int32_t>());
+	float defGain = camHelper_->gain(v4l2Gain.def().get<int32_t>());
+	ctrlMap[&controls::AnalogueGain] = ControlInfo(minGain, maxGain, defGain);
+
+	/* Merge the cotrols */
+	ctrlMap.merge(context_.ctrlMap);
+
+	*ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls);
+}
+
+int IPAC3ISP::configure(const IPAConfigInfo &ipaConfig,
+			ControlInfoMap *ipaControls)
+{
+	sensorControls_ = ipaConfig.sensorControls;
+
+	context_.configuration = {};
+	context_.activeState = {};
+	context_.frameContexts.clear();
+
+	const IPACameraSensorInfo &info = ipaConfig.sensorInfo;
+
+	updateSessionConfiguration(info, ipaConfig.sensorControls);
+
+	updateControls(info, ipaConfig.sensorControls, 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 IPAC3ISP::mapBuffers(const std::vector<IPABuffer> &buffers, bool readOnly)
+{
+	for (const IPABuffer &buffer : buffers) {
+		const FrameBuffer fb(buffer.planes);
+		buffers_.emplace(
+			buffer.id,
+			MappedFrameBuffer(
+				&fb,
+				readOnly ? MappedFrameBuffer::MapFlag::Read
+					 : MappedFrameBuffer::MapFlag::ReadWrite));
+	}
+}
+
+void IPAC3ISP::unmapBuffers(const std::vector<IPABuffer> &buffers)
+{
+	for (const IPABuffer &buffer : buffers) {
+		auto it = buffers_.find(buffer.id);
+		if (it == buffers_.end())
+			continue;
+
+		buffers_.erase(buffer.id);
+	}
+}
+
+void IPAC3ISP::queueRequest(const uint32_t request, const ControlList &controls)
+{
+	IPAFrameContext &frameContext = context_.frameContexts.alloc(request);
+
+	for (auto const &a : algorithms()) {
+		Algorithm *algo = static_cast<Algorithm *>(a.get());
+
+		algo->queueRequest(context_, request, frameContext, controls);
+	}
+}
+
+void IPAC3ISP::computeParams(const uint32_t request, const uint32_t bufferId)
+{
+	IPAFrameContext &frameContext = context_.frameContexts.get(request);
+
+	C3ISPParams params(buffers_.at(bufferId).planes()[0]);
+
+	for (auto const &algo : algorithms())
+		algo->prepare(context_, request, frameContext, &params);
+
+	paramsComputed.emit(request, params.size());
+}
+
+void IPAC3ISP::processStats(const uint32_t request, const uint32_t bufferId,
+			    const ControlList &sensorControls)
+{
+	IPAFrameContext &frameContext = context_.frameContexts.get(request);
+	const c3_isp_stats_info *stats = nullptr;
+
+	stats = reinterpret_cast<c3_isp_stats_info *>(
+		buffers_.at(bufferId).planes()[0].data());
+
+	frameContext.agc.exposure =
+		sensorControls.get(V4L2_CID_EXPOSURE).get<int32_t>();
+	frameContext.agc.sensorGain =
+		camHelper_->gain(sensorControls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>());
+
+	ControlList metadata(controls::controls);
+
+	for (auto const &a : algorithms()) {
+		Algorithm *algo = static_cast<Algorithm *>(a.get());
+
+		algo->process(context_, request, frameContext, stats, metadata);
+	}
+
+	setControls();
+
+	statsProcessed.emit(request, metadata);
+}
+
+void IPAC3ISP::setControls()
+{
+	IPAActiveState &activeState = context_.activeState;
+	uint32_t exposure;
+	uint32_t gain;
+
+	if (activeState.agc.autoEnabled) {
+		exposure = activeState.agc.automatic.exposure;
+		gain = camHelper_->gainCode(activeState.agc.automatic.sensorGain);
+	} else {
+		exposure = activeState.agc.manual.exposure;
+		gain = camHelper_->gainCode(activeState.agc.manual.sensorGain);
+	}
+
+	ControlList ctrls(sensorControls_);
+	ctrls.set(V4L2_CID_EXPOSURE, static_cast<int32_t>(exposure));
+	ctrls.set(V4L2_CID_ANALOGUE_GAIN, static_cast<int32_t>(gain));
+
+	setSensorControls.emit(ctrls);
+}
+
+} /* namespace ipa::c3isp */
+
+/*
+ * External IPA module interface
+ */
+
+extern "C" {
+const struct IPAModuleInfo ipaModuleInfo = {
+	IPA_MODULE_API_VERSION,
+	1,
+	"c3isp",
+	"c3isp",
+};
+
+IPAInterface *ipaCreate()
+{
+	return new ipa::c3isp::IPAC3ISP();
+}
+}
+
+} /* namespace libcamera */
diff --git a/src/ipa/c3-isp/data/meson.build b/src/ipa/c3-isp/data/meson.build
new file mode 100644
index 00000000..75e44bac
--- /dev/null
+++ b/src/ipa/c3-isp/data/meson.build
@@ -0,0 +1,9 @@ 
+# SPDX-License-Identifier: CC0-1.0
+
+conf_files = files([
+
+])
+
+install_data(conf_files,
+             install_dir : ipa_data_dir / 'c3isp',
+             install_tag : 'runtime')
diff --git a/src/ipa/c3-isp/ipa_context.cpp b/src/ipa/c3-isp/ipa_context.cpp
new file mode 100644
index 00000000..8f69bbfb
--- /dev/null
+++ b/src/ipa/c3-isp/ipa_context.cpp
@@ -0,0 +1,101 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Amlogic Inc.
+ *
+ * C3ISP IPA Context
+ */
+
+#include "ipa_context.h"
+
+/**
+ * \file ipa_context.h
+ * \brief Context and state information shared between the algorithms
+ */
+
+namespace libcamera::ipa::c3isp {
+
+/**
+ * \struct IPASessionConfiguration
+ * \brief Session configuration for the IPA module
+ *
+ * The session configuration contains all IPA configuration parameters that
+ * remain constant during the capture session, from IPA module start to stop.
+ * It is typically set during the configure() operation of the IPA module, but
+ * may also be updated in the start() operation.
+ */
+
+/**
+ * \struct IPAActiveState
+ * \brief Active state for algorithms
+ *
+ * The active state contains all algorithm-specific data that needs to be
+ * maintained by algorithms across frames. Unlike the session configuration,
+ * the active state is mutable and constantly updated by algorithms. The active
+ * state is accessible through the IPAContext structure.
+ *
+ * The active state stores two distinct categories of information:
+ *
+ *  - The consolidated value of all algorithm controls. Requests passed to
+ *    the queueRequest() function store values for controls that the
+ *    application wants to modify for that particular frame, and the
+ *    queueRequest() function updates the active state with those values.
+ *    The active state thus contains a consolidated view of the value of all
+ *    controls handled by the algorithm.
+ *
+ *  - The value of parameters computed by the algorithm when running in auto
+ *    mode. Algorithms running in auto mode compute new parameters every
+ *    time statistics buffers are received (either synchronously, or
+ *    possibly in a background thread). The latest computed value of those
+ *    parameters is stored in the active state in the process() function.
+ *
+ * Each of the members in the active state belongs to a specific algorithm. A
+ * member may be read by any algorithm, but shall only be written by its owner.
+ */
+
+/**
+ * \struct IPAFrameContext
+ * \brief Per-frame context for algorithms
+ *
+ * The frame context stores two distinct categories of information:
+ *
+ * - The value of the controls to be applied to the frame. These values are
+ *   typically set in the queueRequest() function, from the consolidated
+ *   control values stored in the active state. The frame context thus stores
+ *   values for all controls related to the algorithm, not limited to the
+ *   controls specified in the corresponding request, but consolidated from all
+ *   requests that have been queued so far.
+ *
+ *   For controls that can be set manually or computed by an algorithm
+ *   (depending on the algorithm operation mode), such as for instance the
+ *   colour gains for the AWB algorithm, the control value will be stored in
+ *   the frame context in the queueRequest() function only when operating in
+ *   manual mode. When operating in auto mode, the values are computed by the
+ *   algorithm in process(), stored in the active state, and copied to the
+ *   frame context in prepare(), just before being stored in the ISP parameters
+ *   buffer.
+ *
+ *   The queueRequest() function can also store ancillary data in the frame
+ *   context, such as flags to indicate if (and what) control values have
+ *   changed compared to the previous request.
+ *
+ * - Status information computed by the algorithm for a frame. For instance,
+ *   the colour temperature estimated by the AWB algorithm from ISP statistics
+ *   calculated on a frame is stored in the frame context for that frame in
+ *   the process() function.
+ */
+
+/**
+ * \struct IPAContext
+ * \brief Global IPA context data shared between all algorithms
+ *
+ * \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
+ */
+
+} /* namespace libcamera::ipa::c3isp */
diff --git a/src/ipa/c3-isp/ipa_context.h b/src/ipa/c3-isp/ipa_context.h
new file mode 100644
index 00000000..227d657a
--- /dev/null
+++ b/src/ipa/c3-isp/ipa_context.h
@@ -0,0 +1,87 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Amlogic Inc.
+ *
+ * C3ISP IPA Context
+ */
+
+#pragma once
+
+#include <libcamera/base/utils.h>
+#include <libcamera/controls.h>
+
+#include <libipa/fc_queue.h>
+
+namespace libcamera {
+
+namespace ipa::c3isp {
+
+struct IPASessionConfiguration {
+	struct {
+		utils::Duration minShutterSpeed;
+		utils::Duration maxShutterSpeed;
+		uint32_t defaultExposure;
+		double minAnalogueGain;
+		double maxAnalogueGain;
+	} agc;
+
+	struct {
+		utils::Duration lineDuration;
+		uint32_t blackLevel;
+		Size size;
+	} sensor;
+};
+
+struct IPAActiveState {
+	struct {
+		struct {
+			uint32_t exposure;
+			double sensorGain;
+			double ispGain;
+		} automatic;
+		struct {
+			uint32_t exposure;
+			double sensorGain;
+			double ispGain;
+		} manual;
+		bool autoEnabled;
+		uint32_t constraintMode;
+		uint32_t exposureMode;
+	} agc;
+
+	struct {
+		double rGain;
+		double bGain;
+		uint32_t temperatureK;
+	} awb;
+};
+
+struct IPAFrameContext : public FrameContext {
+	struct {
+		uint32_t exposure;
+		double sensorGain;
+	} agc;
+
+	struct {
+		double rGain;
+		double bGain;
+	} awb;
+};
+
+struct IPAContext {
+	IPAContext(unsigned int frameContextSize)
+		: frameContexts(frameContextSize)
+	{
+	}
+
+	IPASessionConfiguration configuration;
+	IPAActiveState activeState;
+
+	FCQueue<IPAFrameContext> frameContexts;
+
+	ControlInfoMap::Map ctrlMap;
+};
+
+} /* namespace ipa::c3isp */
+
+} /* namespace libcamera*/
diff --git a/src/ipa/c3-isp/meson.build b/src/ipa/c3-isp/meson.build
new file mode 100644
index 00000000..fa5c6be0
--- /dev/null
+++ b/src/ipa/c3-isp/meson.build
@@ -0,0 +1,32 @@ 
+# SPDX-License-Identifier: CC0-1.0
+
+subdir('algorithms')
+subdir('data')
+
+ipa_name = 'ipa_c3isp'
+
+c3isp_ipa_sources = files([
+    'ipa_context.cpp',
+    'params.cpp',
+    'c3-isp.cpp',
+])
+
+c3isp_ipa_sources += c3isp_ipa_algorithms
+
+mod = shared_module(ipa_name, c3isp_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/c3-isp/module.h b/src/ipa/c3-isp/module.h
new file mode 100644
index 00000000..a1116396
--- /dev/null
+++ b/src/ipa/c3-isp/module.h
@@ -0,0 +1,28 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Amlogic
+ *
+ * C3ISP IPA Module
+ */
+
+#pragma once
+
+#include <linux/c3-isp-config.h>
+
+#include <libcamera/ipa/c3-isp_ipa_interface.h>
+
+#include <libipa/module.h>
+
+#include "ipa_context.h"
+#include "params.h"
+
+namespace libcamera {
+
+namespace ipa::c3isp {
+
+using Module = ipa::Module<IPAContext, IPAFrameContext, IPACameraSensorInfo,
+			   C3ISPParams, c3_isp_stats_info>;
+
+} /* namespace ipa::c3isp */
+
+} /* namespace libcamera*/
diff --git a/src/ipa/c3-isp/params.cpp b/src/ipa/c3-isp/params.cpp
new file mode 100644
index 00000000..d5c57da3
--- /dev/null
+++ b/src/ipa/c3-isp/params.cpp
@@ -0,0 +1,127 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Amlogic Inc.
+ *
+ * C3ISP ISP Parameters
+ */
+
+#include "params.h"
+
+#include <map>
+#include <stddef.h>
+#include <string.h>
+
+#include <linux/c3-isp-config.h>
+#include <linux/videodev2.h>
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(C3ISPParams)
+
+namespace ipa::c3isp {
+
+namespace {
+
+struct BlockTypeInfo {
+	enum c3_isp_params_block_type type;
+	size_t size;
+};
+
+#define C3ISP_BLOCK_TYPE_ENTRY(block, id, type)                      \
+	{                                                            \
+		BlockType::block,                                    \
+		{                                                    \
+			C3_ISP_PARAMS_BLOCK_##id,                    \
+				sizeof(struct c3_isp_params_##type), \
+		}                                                    \
+	}
+
+const std::map<BlockType, BlockTypeInfo> kBlockTypeInfo = {
+	C3ISP_BLOCK_TYPE_ENTRY(AWBGains, AWB_GAINS, awb_gains),
+	C3ISP_BLOCK_TYPE_ENTRY(AWBConfig, AWB_CONFIG, awb_config),
+	C3ISP_BLOCK_TYPE_ENTRY(AEConfig, AE_CONFIG, ae_config),
+	C3ISP_BLOCK_TYPE_ENTRY(AFConfig, AF_CONFIG, af_config),
+	C3ISP_BLOCK_TYPE_ENTRY(PostGamma, PST_GAMMA, pst_gamma),
+	C3ISP_BLOCK_TYPE_ENTRY(Ccm, CCM, ccm),
+	C3ISP_BLOCK_TYPE_ENTRY(Csc, CSC, csc),
+	C3ISP_BLOCK_TYPE_ENTRY(Blc, BLC, blc),
+};
+
+} /* namespace */
+
+C3ISPParamsBlockBase::C3ISPParamsBlockBase(BlockType type,
+					   const Span<uint8_t> &data)
+	: type_(type), data_(data)
+{
+	header_ = data.subspan(0, sizeof(c3_isp_params_block_header));
+}
+
+void C3ISPParamsBlockBase::setEnabled(uint16_t flags)
+{
+	struct c3_isp_params_block_header *header =
+		reinterpret_cast<struct c3_isp_params_block_header *>(header_.data());
+
+	header->flags = flags;
+}
+
+C3ISPParams::C3ISPParams(Span<uint8_t> data)
+	: data_(data), used_(0)
+{
+	struct c3_isp_params_cfg *buffer =
+		reinterpret_cast<struct c3_isp_params_cfg *>(data.data());
+
+	buffer->version = C3_ISP_PARAMS_BUFFER_V0;
+	buffer->data_size = 0;
+
+	used_ += offsetof(struct c3_isp_params_cfg, data);
+}
+
+Span<uint8_t> C3ISPParams::block(BlockType type)
+{
+	auto infoIt = kBlockTypeInfo.find(type);
+	if (infoIt == kBlockTypeInfo.end()) {
+		LOG(C3ISPParams, Error)
+			<< "Invalid parameters type "
+			<< utils::to_underlying(type);
+		return {};
+	}
+
+	const BlockTypeInfo &info = infoIt->second;
+
+	auto cacheIt = blocks_.find(type);
+	if (cacheIt != blocks_.end())
+		return cacheIt->second;
+
+	size_t size = info.size;
+	if (size > data_.size() - used_) {
+		LOG(C3ISPParams, Error)
+			<< "No enough remaining space "
+			<< utils::to_underlying(type);
+		return {};
+	}
+
+	Span<uint8_t> block = data_.subspan(used_, info.size);
+	used_ += block.size();
+
+	struct c3_isp_params_cfg *buffer =
+		reinterpret_cast<struct c3_isp_params_cfg *>(data_.data());
+	buffer->data_size += block.size();
+
+	memset(block.data(), 0, block.size());
+
+	struct c3_isp_params_block_header *header =
+		reinterpret_cast<struct c3_isp_params_block_header *>(block.data());
+	header->type = info.type;
+	header->size = block.size();
+
+	blocks_[type] = block;
+
+	return block;
+}
+
+} /* namespace ipa::c3isp */
+
+} /* namespace libcamera */
diff --git a/src/ipa/c3-isp/params.h b/src/ipa/c3-isp/params.h
new file mode 100644
index 00000000..9bb3877b
--- /dev/null
+++ b/src/ipa/c3-isp/params.h
@@ -0,0 +1,133 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Amlogic Inc.
+ *
+ * C3ISP ISP Parameters
+ */
+
+#pragma once
+
+#include <map>
+#include <stdint.h>
+
+#include <linux/c3-isp-config.h>
+
+#include <libcamera/base/class.h>
+#include <libcamera/base/span.h>
+
+namespace libcamera {
+
+namespace ipa::c3isp {
+
+enum class BlockType {
+	AWBGains,
+	AWBConfig,
+	AEConfig,
+	AFConfig,
+	PostGamma,
+	Ccm,
+	Csc,
+	Blc,
+};
+
+namespace details {
+
+template<BlockType B>
+struct block_type {
+};
+
+#define C3ISP_DEFINE_BLOCK_TYPE(blocktype, blockStruct)          \
+	template<>                                               \
+	struct block_type<BlockType::blocktype> {                \
+		using type = struct c3_isp_params_##blockStruct; \
+	};
+
+C3ISP_DEFINE_BLOCK_TYPE(AWBGains, awb_gains)
+C3ISP_DEFINE_BLOCK_TYPE(AWBConfig, awb_config)
+C3ISP_DEFINE_BLOCK_TYPE(AEConfig, ae_config)
+C3ISP_DEFINE_BLOCK_TYPE(AFConfig, af_config)
+C3ISP_DEFINE_BLOCK_TYPE(PostGamma, pst_gamma)
+C3ISP_DEFINE_BLOCK_TYPE(Ccm, ccm)
+C3ISP_DEFINE_BLOCK_TYPE(Csc, csc)
+C3ISP_DEFINE_BLOCK_TYPE(Blc, blc)
+
+} /* namespace details */
+
+class C3ISPParams;
+
+class C3ISPParamsBlockBase
+{
+public:
+	C3ISPParamsBlockBase(BlockType type, const Span<uint8_t> &data);
+
+	Span<uint8_t> data() const { return data_; }
+
+	void setEnabled(uint16_t flags);
+
+private:
+	LIBCAMERA_DISABLE_COPY(C3ISPParamsBlockBase)
+
+	BlockType type_;
+	Span<uint8_t> header_;
+	Span<uint8_t> data_;
+};
+
+template<BlockType B>
+class C3ISPParamsBlock : public C3ISPParamsBlockBase
+{
+public:
+	using Type = typename details::block_type<B>::type;
+
+	C3ISPParamsBlock(const Span<uint8_t> &data)
+		: C3ISPParamsBlockBase(B, data)
+	{
+	}
+
+	const Type *operator->() const
+	{
+		return reinterpret_cast<const Type *>(data().data());
+	}
+
+	Type *operator->()
+	{
+		return reinterpret_cast<Type *>(data().data());
+	}
+
+	const Type &operator*() const &
+	{
+		return *reinterpret_cast<const Type *>(data().data());
+	}
+
+	const Type &operator*() &
+	{
+		return *reinterpret_cast<Type *>(data().data());
+	}
+};
+
+class C3ISPParams
+{
+public:
+	C3ISPParams(Span<uint8_t> data);
+
+	template<BlockType B>
+	C3ISPParamsBlock<B> block()
+	{
+		return C3ISPParamsBlock<B>(block(B));
+	}
+
+	size_t size() const { return used_; }
+
+private:
+	friend class C3ISPParamsBlockBase;
+
+	Span<uint8_t> block(BlockType type);
+
+	Span<uint8_t> data_;
+	size_t used_;
+
+	std::map<BlockType, Span<uint8_t>> blocks_;
+};
+
+} /* namespace ipa::c3isp */
+
+} /* namespace libcamera */