@@ -32,6 +32,7 @@ RECURSIVE = YES
EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \
@TOP_BUILDDIR@/include/libcamera/ipa/*_proxy.h \
@TOP_BUILDDIR@/include/libcamera/ipa/ipu3_*.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/vimc_*.h
new file mode 100644
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+module ipa.mali_c55;
+
+import "include/libcamera/ipa/core.mojom";
+
+struct IPAConfigInfo {
+ libcamera.IPACameraSensorInfo sensorInfo;
+ libcamera.ControlInfoMap sensorControls;
+};
+
+interface IPAMaliC55Interface {
+ init(libcamera.IPASettings settings, IPAConfigInfo configInfo)
+ => (int32 ret, libcamera.ControlInfoMap ipaControls);
+ start() => (int32 ret);
+ stop();
+
+ configure(IPAConfigInfo configInfo, uint8 bayerOrder)
+ => (int32 ret, libcamera.ControlInfoMap ipaControls);
+
+ mapBuffers(array<libcamera.IPABuffer> buffers, bool readOnly);
+ unmapBuffers(array<libcamera.IPABuffer> buffers);
+
+ [async] queueRequest(uint32 request, libcamera.ControlList reqControls);
+ [async] fillParams(uint32 request, uint32 bufferId);
+ [async] processStats(uint32 request, uint32 bufferId,
+ libcamera.ControlList sensorControls);
+};
+
+interface IPAMaliC55EventInterface {
+ paramsComputed(uint32 request);
+ statsProcessed(uint32 request, libcamera.ControlList metadata);
+ setSensorControls(libcamera.ControlList sensorControls);
+};
@@ -64,6 +64,7 @@ libcamera_ipa_headers += custom_target('core_ipa_serializer_h',
# Mapping from pipeline handler name to mojom file
pipeline_ipa_mojom_mapping = {
'ipu3': 'ipu3.mojom',
+ 'mali-c55': 'mali-c55.mojom',
'rkisp1': 'rkisp1.mojom',
'rpi/vc4': 'raspberrypi.mojom',
'simple': 'soft.mojom',
@@ -32,7 +32,7 @@ option('gstreamer',
option('ipas',
type : 'array',
- choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'simple', 'vimc'],
+ choices : ['ipu3', 'mali-c55', 'rkisp1', 'rpi/vc4', 'simple', 'vimc'],
description : 'Select which IPA modules to build')
option('lc-compliance',
new file mode 100644
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * algorithm.h - Mali-C55 control algorithm interface
+ */
+
+#pragma once
+
+#include <linux/mali-c55-config.h>
+
+#include <libipa/algorithm.h>
+
+#include "module.h"
+
+namespace libcamera {
+
+namespace ipa::mali_c55 {
+
+class Algorithm : public libcamera::ipa::Algorithm<Module>
+{
+};
+
+union mali_c55_params_block {
+ struct mali_c55_params_block_header *header;
+ struct mali_c55_params_sensor_off_preshading *sensor_offs;
+ struct mali_c55_params_aexp_hist *aexp_hist;
+ struct mali_c55_params_aexp_weights *aexp_weights;
+ struct mali_c55_params_digital_gain *digital_gain;
+ struct mali_c55_params_awb_gains *awb_gains;
+ struct mali_c55_params_awb_config *awb_config;
+ struct mali_c55_params_mesh_shading_config *shading_config;
+ struct mali_c55_params_mesh_shading_selection *shading_selection;
+ __u8 *data;
+};
+
+} /* namespace ipa::mali_c55 */
+
+} /* namespace libcamera */
new file mode 100644
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: CC0-1.0
+
+mali_c55_ipa_algorithms = files([
+])
new file mode 100644
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: CC0-1.0
+
+conf_files = files([
+ 'uncalibrated.yaml'
+])
+
+install_data(conf_files,
+ install_dir : ipa_data_dir / 'mali-c55')
new file mode 100644
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: CC0-1.0
+%YAML 1.1
+---
+version: 1
+algorithms:
+...
new file mode 100644
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * ipa_context.cpp - MaliC55 IPA Context
+ */
+
+#include "ipa_context.h"
+
+/**
+ * \file ipa_context.h
+ * \brief Context and state information shared between the algorithms
+ */
+
+namespace libcamera::ipa::mali_c55 {
+
+/**
+ * \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::mali_c55 */
new file mode 100644
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * ipa_context.h - Mali-C55 IPA Context
+ */
+
+#pragma once
+
+#include <libcamera/controls.h>
+
+#include <libipa/fc_queue.h>
+
+namespace libcamera {
+
+namespace ipa::mali_c55 {
+
+struct IPASessionConfiguration {
+};
+
+struct IPAActiveState {
+};
+
+struct IPAFrameContext : public FrameContext {
+ struct {
+ uint32_t exposure;
+ double sensorGain;
+ } agc;
+};
+
+struct IPAContext {
+ IPASessionConfiguration configuration;
+ IPAActiveState activeState;
+
+ FCQueue<IPAFrameContext> frameContexts;
+
+ ControlInfoMap::Map ctrlMap;
+};
+
+} /* namespace ipa::mali_c55 */
+
+} /* namespace libcamera*/
new file mode 100644
@@ -0,0 +1,339 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023, Ideas on Board Oy
+ *
+ * mali-c55.cpp - Mali-C55 ISP image processing algorithms
+ */
+
+#include <map>
+#include <string.h>
+#include <vector>
+
+#include <linux/mali-c55-config.h>
+#include <linux/v4l2-controls.h>
+
+#include <libcamera/base/file.h>
+#include <libcamera/base/log.h>
+
+#include <libcamera/control_ids.h>
+#include <libcamera/ipa/ipa_interface.h>
+#include <libcamera/ipa/ipa_module_info.h>
+#include <libcamera/ipa/mali-c55_ipa_interface.h>
+
+#include "libcamera/internal/bayer_format.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"
+
+namespace libcamera {
+
+LOG_DEFINE_CATEGORY(IPAMaliC55)
+
+namespace ipa::mali_c55 {
+
+/* Maximum number of frame contexts to be held */
+static constexpr uint32_t kMaxFrameContexts = 16;
+
+class IPAMaliC55 : public IPAMaliC55Interface, public Module
+{
+public:
+ IPAMaliC55();
+
+ int init(const IPASettings &settings, const IPAConfigInfo &ipaConfig,
+ ControlInfoMap *ipaControls) override;
+ int start() override;
+ void stop() override;
+ int configure(const IPAConfigInfo &ipaConfig, uint8_t bayerOrder,
+ 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 fillParams(unsigned int request, uint32_t bufferId) override;
+ void processStats(unsigned int request, unsigned int bufferId,
+ const ControlList &sensorControls) override;
+
+protected:
+ std::string logPrefix() const override;
+
+private:
+ 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_;
+};
+
+namespace {
+
+} /* namespace */
+
+IPAMaliC55::IPAMaliC55()
+ : context_({ {}, {}, { kMaxFrameContexts }, {} })
+{
+}
+
+std::string IPAMaliC55::logPrefix() const
+{
+ return "mali-c55";
+}
+
+int IPAMaliC55::init(const IPASettings &settings, const IPAConfigInfo &ipaConfig,
+ ControlInfoMap *ipaControls)
+{
+ camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
+ if (!camHelper_) {
+ LOG(IPAMaliC55, 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(IPAMaliC55, 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(IPAMaliC55, 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;
+}
+
+void IPAMaliC55::setControls()
+{
+ ControlList ctrls(sensorControls_);
+
+ setSensorControls.emit(ctrls);
+}
+
+int IPAMaliC55::start()
+{
+ return 0;
+}
+
+void IPAMaliC55::stop()
+{
+ context_.frameContexts.clear();
+}
+
+void IPAMaliC55::updateControls(const IPACameraSensorInfo &sensorInfo,
+ const ControlInfoMap &sensorControls,
+ ControlInfoMap *ipaControls)
+{
+ ControlInfoMap::Map ctrlMap;
+
+ /*
+ * Compute the frame duration limits.
+ *
+ * The frame length is computed assuming a fixed line length combined
+ * with the vertical frame sizes.
+ */
+ 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 exposure time limits from the V4L2_CID_EXPOSURE control
+ * limits and the line duration.
+ */
+ double lineDuration = sensorInfo.minLineLength / sensorInfo.pixelRate;
+
+ 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 in any controls that we support either statically or from the
+ * algorithms.
+ */
+ ctrlMap.merge(context_.ctrlMap);
+
+ *ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls);
+}
+
+int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig,
+ [[maybe_unused]] uint8_t bayerOrder,
+ ControlInfoMap *ipaControls)
+{
+ sensorControls_ = ipaConfig.sensorControls;
+
+ /* Clear the IPA context before the streaming session. */
+ context_.configuration = {};
+ context_.activeState = {};
+ context_.frameContexts.clear();
+
+ const IPACameraSensorInfo &info = ipaConfig.sensorInfo;
+
+ 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 IPAMaliC55::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 IPAMaliC55::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 IPAMaliC55::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 IPAMaliC55::fillParams(unsigned int request,
+ [[maybe_unused]] uint32_t bufferId)
+{
+ struct mali_c55_params_buffer *params;
+ IPAFrameContext &frameContext = context_.frameContexts.get(request);
+
+ params = reinterpret_cast<mali_c55_params_buffer *>(
+ buffers_.at(bufferId).planes()[0].data());
+ memset(params, 0, sizeof(mali_c55_params_buffer));
+
+ params->version = MALI_C55_PARAM_BUFFER_V1;
+
+ for (auto const &algo : algorithms()) {
+ algo->prepare(context_, request, frameContext, params);
+
+ ASSERT(params->total_size <= MALI_C55_PARAMS_MAX_SIZE);
+ }
+
+ paramsComputed.emit(request);
+}
+
+void IPAMaliC55::processStats(unsigned int request, unsigned int bufferId,
+ const ControlList &sensorControls)
+{
+ IPAFrameContext &frameContext = context_.frameContexts.get(request);
+ const mali_c55_stats_buffer *stats = nullptr;
+
+ stats = reinterpret_cast<mali_c55_stats_buffer *>(
+ 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);
+}
+
+} /* namespace ipa::mali_c55 */
+
+/*
+ * External IPA module interface
+ */
+extern "C" {
+const struct IPAModuleInfo ipaModuleInfo = {
+ IPA_MODULE_API_VERSION,
+ 1,
+ "mali-c55",
+ "mali-c55",
+};
+
+IPAInterface *ipaCreate()
+{
+ return new ipa::mali_c55::IPAMaliC55();
+}
+
+} /* extern "C" */
+
+} /* namespace libcamera */
new file mode 100644
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: CC0-1.0
+
+subdir('algorithms')
+subdir('data')
+
+ipa_name = 'ipa_mali_c55'
+
+mali_c55_ipa_sources = files([
+ 'ipa_context.cpp',
+ 'mali-c55.cpp'
+])
+
+mali_c55_ipa_sources += mali_c55_ipa_algorithms
+
+mod = shared_module(ipa_name,
+ mali_c55_ipa_sources,
+ name_prefix : '',
+ include_directories : [ipa_includes, libipa_includes],
+ dependencies : libcamera_private,
+ link_with : libipa,
+ 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
new file mode 100644
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ *
+ * module.h - Mali-C55 IPA Module
+ */
+
+#pragma once
+
+#include <linux/mali-c55-config.h>
+
+#include <libcamera/ipa/mali-c55_ipa_interface.h>
+
+#include <libipa/module.h>
+
+#include "ipa_context.h"
+
+namespace libcamera {
+
+namespace ipa::mali_c55 {
+
+using Module = ipa::Module<IPAContext, IPAFrameContext, IPACameraSensorInfo,
+ mali_c55_params_buffer, mali_c55_stats_buffer>;
+
+} /* namespace ipa::mali_c55 */
+
+} /* namespace libcamera*/