Files
external_libcamera/src/ipa/mali-c55/mali-c55.cpp
Jacopo Mondi 202e330789 ipa: mali-c55: Retain Camera::controls() after ipa->configure()
Similar to what commit acfd602767 ("ipa: rkisp1: Fix algorithm controls
vanish after configure") did for the RkISP1 IPA, replace the usage of
unordered_map::merge() at updateControls() time with
unordered_map::insert().

As unordered_map::merge moves items from the source map, it deletes
controls registered at algorithms initialization time in the
ipaContext.ctrlMap. As at configure() time updateControls() is called
again and the list of Camera controls is refreshed, the controls
registered at algorithms initialization time are lost.

Fixes: fe989ee514 ("ipa: mali-c55: Add Mali-C55 ISP IPA module")
Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>
Reviewed-by: Barnabás Pőcze <barnabas.pocze@ideasonboard.com>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
2026-01-23 17:39:29 +01:00

394 lines
11 KiB
C++

/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2023, Ideas on Board Oy
*
* Mali-C55 ISP image processing algorithms
*/
#include <array>
#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/base/span.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"
#include "params.h"
namespace libcamera {
LOG_DEFINE_CATEGORY(IPAMaliC55)
using namespace std::literals::chrono_literals;
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 updateSessionConfiguration(const IPACameraSensorInfo &info,
const ControlInfoMap &sensorControls,
BayerFormat::Order bayerOrder);
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()
{
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);
}
int IPAMaliC55::start()
{
return 0;
}
void IPAMaliC55::stop()
{
context_.frameContexts.clear();
}
void IPAMaliC55::updateSessionConfiguration(const IPACameraSensorInfo &info,
const ControlInfoMap &sensorControls,
BayerFormat::Order bayerOrder)
{
context_.configuration.sensor.bayerOrder = bayerOrder;
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>();
/*
* When the AGC computes the new exposure values for a frame, it needs
* to know the limits for shutter speed and analogue gain.
* As it depends on the sensor, update it with the controls.
*
* \todo take VBLANK into account for maximum shutter speed
*/
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);
if (camHelper_->blackLevel().has_value()) {
/*
* The black level from CameraSensorHelper is a 16-bit value.
* The Mali-C55 ISP expects 20-bit settings, so we shift it to
* the appropriate width
*/
context_.configuration.sensor.blackLevel =
camHelper_->blackLevel().value() << 4;
}
}
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],
Span<const int64_t, 2>{ { frameDurations[2], 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.insert(context_.ctrlMap.begin(), context_.ctrlMap.end());
*ipaControls = ControlInfoMap(std::move(ctrlMap), controls::controls);
}
int IPAMaliC55::configure(const IPAConfigInfo &ipaConfig, 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;
updateSessionConfiguration(info, ipaConfig.sensorControls,
static_cast<BayerFormat::Order>(bayerOrder));
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)
{
IPAFrameContext &frameContext = context_.frameContexts.get(request);
MaliC55Params params(buffers_.at(bufferId).planes()[0]);
for (auto const &algo : algorithms())
algo->prepare(context_, request, frameContext, &params);
paramsComputed.emit(request, params.bytesused());
}
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 */