/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2019, Google Inc. * * Pipeline handler for Rockchip ISP1 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libcamera/internal/camera.h" #include "libcamera/internal/camera_sensor.h" #include "libcamera/internal/camera_sensor_properties.h" #include "libcamera/internal/converter/converter_dw100.h" #include "libcamera/internal/delayed_controls.h" #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/framebuffer.h" #include "libcamera/internal/ipa_manager.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/media_pipeline.h" #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/request.h" #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" #include "libcamera/internal/yaml_parser.h" #include "rkisp1_path.h" namespace libcamera { LOG_DEFINE_CATEGORY(RkISP1) class PipelineHandlerRkISP1; class RkISP1CameraData; struct RkISP1FrameInfo { unsigned int frame; Request *request; FrameBuffer *paramBuffer; FrameBuffer *statBuffer; FrameBuffer *mainPathBuffer; FrameBuffer *selfPathBuffer; bool paramDequeued; bool metadataProcessed; }; class RkISP1Frames { public: RkISP1Frames(PipelineHandler *pipe); RkISP1FrameInfo *create(const RkISP1CameraData *data, Request *request, bool isRaw); int destroy(unsigned int frame); void clear(); RkISP1FrameInfo *find(unsigned int frame); RkISP1FrameInfo *find(FrameBuffer *buffer); RkISP1FrameInfo *find(Request *request); private: PipelineHandlerRkISP1 *pipe_; std::map frameInfo_; }; class RkISP1CameraData : public Camera::Private { public: RkISP1CameraData(PipelineHandler *pipe, RkISP1MainPath *mainPath, RkISP1SelfPath *selfPath) : Camera::Private(pipe), frame_(0), frameInfo_(pipe), mainPath_(mainPath), selfPath_(selfPath), canUseDewarper_(false), usesDewarper_(false) { } PipelineHandlerRkISP1 *pipe(); const PipelineHandlerRkISP1 *pipe() const; int loadIPA(unsigned int hwRevision, uint32_t supportedBlocks); Stream mainPathStream_; Stream selfPathStream_; std::unique_ptr sensor_; std::unique_ptr delayedCtrls_; unsigned int frame_; std::vector ipaBuffers_; RkISP1Frames frameInfo_; RkISP1MainPath *mainPath_; RkISP1SelfPath *selfPath_; std::unique_ptr ipa_; ControlInfoMap ipaControls_; /* * All entities in the pipeline, from the camera sensor to the RKISP1. */ MediaPipeline pipe_; bool canUseDewarper_; bool usesDewarper_; private: void paramsComputed(unsigned int frame, unsigned int bytesused); void setSensorControls(unsigned int frame, const ControlList &sensorControls); void metadataReady(unsigned int frame, const ControlList &metadata); int loadTuningFile(const std::string &file); }; class RkISP1CameraConfiguration : public CameraConfiguration { public: RkISP1CameraConfiguration(Camera *camera, RkISP1CameraData *data); Status validate() override; const V4L2SubdeviceFormat &sensorFormat() { return sensorFormat_; } const Transform &combinedTransform() { return combinedTransform_; } private: bool fitsAllPaths(const StreamConfiguration &cfg); /* * The RkISP1CameraData instance is guaranteed to be valid as long as the * corresponding Camera instance is valid. In order to borrow a * reference to the camera data, store a new reference to the camera. */ std::shared_ptr camera_; const RkISP1CameraData *data_; V4L2SubdeviceFormat sensorFormat_; Transform combinedTransform_; }; namespace { /* * Maximum number of requests that shall be queued into the pipeline to keep * the regulation fast. * \todo This needs revisiting as soon as buffers got decoupled from requests * and/or a fast path for controls was implemented. */ static constexpr unsigned int kRkISP1MaxQueuedRequests = 4; /* * This many internal buffers (or rather parameter and statistics buffer * pairs) ensures that the pipeline runs smoothly, without frame drops. */ static constexpr unsigned int kRkISP1MinBufferCount = 4; } /* namespace */ class PipelineHandlerRkISP1 : public PipelineHandler { public: PipelineHandlerRkISP1(CameraManager *manager); std::unique_ptr generateConfiguration(Camera *camera, Span roles) override; int configure(Camera *camera, CameraConfiguration *config) override; int exportFrameBuffers(Camera *camera, Stream *stream, std::vector> *buffers) override; int start(Camera *camera, const ControlList *controls) override; void stopDevice(Camera *camera) override; int queueRequestDevice(Camera *camera, Request *request) override; bool match(DeviceEnumerator *enumerator) override; private: static constexpr Size kRkISP1PreviewSize = { 1920, 1080 }; RkISP1CameraData *cameraData(Camera *camera) { return static_cast(camera->_d()); } friend RkISP1CameraData; friend RkISP1CameraConfiguration; friend RkISP1Frames; int initLinks(Camera *camera, const RkISP1CameraConfiguration &config); int createCamera(MediaEntity *sensor); void tryCompleteRequest(RkISP1FrameInfo *info); void cancelDewarpRequest(RkISP1FrameInfo *info); void imageBufferReady(FrameBuffer *buffer); void paramBufferReady(FrameBuffer *buffer); void statBufferReady(FrameBuffer *buffer); void dewarpBufferReady(FrameBuffer *buffer); void frameStart(uint32_t sequence); int allocateBuffers(Camera *camera); int freeBuffers(Camera *camera); int updateControls(RkISP1CameraData *data); std::shared_ptr media_; std::unique_ptr isp_; std::unique_ptr param_; std::unique_ptr stat_; bool hasSelfPath_; bool isRaw_; RkISP1MainPath mainPath_; RkISP1SelfPath selfPath_; std::unique_ptr dewarper_; /* Internal buffers used when dewarper is being used */ std::vector> mainPathBuffers_; std::queue availableMainPathBuffers_; std::vector> paramBuffers_; std::vector> statBuffers_; std::queue availableParamBuffers_; std::queue availableStatBuffers_; Camera *activeCamera_; }; RkISP1Frames::RkISP1Frames(PipelineHandler *pipe) : pipe_(static_cast(pipe)) { } RkISP1FrameInfo *RkISP1Frames::create(const RkISP1CameraData *data, Request *request, bool isRaw) { unsigned int frame = data->frame_; FrameBuffer *paramBuffer = nullptr; FrameBuffer *statBuffer = nullptr; FrameBuffer *mainPathBuffer = nullptr; FrameBuffer *selfPathBuffer = nullptr; if (!isRaw) { if (pipe_->availableParamBuffers_.empty()) { LOG(RkISP1, Error) << "Parameters buffer underrun"; return nullptr; } if (pipe_->availableStatBuffers_.empty()) { LOG(RkISP1, Error) << "Statistic buffer underrun"; return nullptr; } paramBuffer = pipe_->availableParamBuffers_.front(); pipe_->availableParamBuffers_.pop(); statBuffer = pipe_->availableStatBuffers_.front(); pipe_->availableStatBuffers_.pop(); if (data->usesDewarper_) { mainPathBuffer = pipe_->availableMainPathBuffers_.front(); pipe_->availableMainPathBuffers_.pop(); } } if (!mainPathBuffer) mainPathBuffer = request->findBuffer(&data->mainPathStream_); selfPathBuffer = request->findBuffer(&data->selfPathStream_); auto [it, inserted] = frameInfo_.try_emplace(frame); ASSERT(inserted); auto &info = it->second; info.frame = frame; info.request = request; info.paramBuffer = paramBuffer; info.mainPathBuffer = mainPathBuffer; info.selfPathBuffer = selfPathBuffer; info.statBuffer = statBuffer; info.paramDequeued = false; info.metadataProcessed = false; return &info; } int RkISP1Frames::destroy(unsigned int frame) { auto it = frameInfo_.find(frame); if (it == frameInfo_.end()) return -ENOENT; auto &info = it->second; pipe_->availableParamBuffers_.push(info.paramBuffer); pipe_->availableStatBuffers_.push(info.statBuffer); pipe_->availableMainPathBuffers_.push(info.mainPathBuffer); frameInfo_.erase(it); return 0; } void RkISP1Frames::clear() { for (const auto &[frame, info] : frameInfo_) { pipe_->availableParamBuffers_.push(info.paramBuffer); pipe_->availableStatBuffers_.push(info.statBuffer); pipe_->availableMainPathBuffers_.push(info.mainPathBuffer); } frameInfo_.clear(); } RkISP1FrameInfo *RkISP1Frames::find(unsigned int frame) { auto itInfo = frameInfo_.find(frame); if (itInfo != frameInfo_.end()) return &itInfo->second; LOG(RkISP1, Fatal) << "Can't locate info from frame"; return nullptr; } RkISP1FrameInfo *RkISP1Frames::find(FrameBuffer *buffer) { for (auto &[frame, info] : frameInfo_) { if (info.paramBuffer == buffer || info.statBuffer == buffer || info.mainPathBuffer == buffer || info.selfPathBuffer == buffer) return &info; } LOG(RkISP1, Fatal) << "Can't locate info from buffer"; return nullptr; } RkISP1FrameInfo *RkISP1Frames::find(Request *request) { for (auto &[frame, info] : frameInfo_) { if (info.request == request) return &info; } LOG(RkISP1, Fatal) << "Can't locate info from request"; return nullptr; } PipelineHandlerRkISP1 *RkISP1CameraData::pipe() { return static_cast(Camera::Private::pipe()); } const PipelineHandlerRkISP1 *RkISP1CameraData::pipe() const { return static_cast(Camera::Private::pipe()); } int RkISP1CameraData::loadIPA(unsigned int hwRevision, uint32_t supportedBlocks) { ipa_ = IPAManager::createIPA(pipe(), 1, 1); if (!ipa_) return -ENOENT; ipa_->setSensorControls.connect(this, &RkISP1CameraData::setSensorControls); ipa_->paramsComputed.connect(this, &RkISP1CameraData::paramsComputed); ipa_->metadataReady.connect(this, &RkISP1CameraData::metadataReady); /* The IPA tuning file is made from the sensor name. */ std::string ipaTuningFile = ipa_->configurationFile(sensor_->model() + ".yaml", "uncalibrated.yaml"); IPACameraSensorInfo sensorInfo{}; int ret = sensor_->sensorInfo(&sensorInfo); if (ret) { LOG(RkISP1, Error) << "Camera sensor information not available"; return ret; } ret = ipa_->init({ ipaTuningFile, sensor_->model() }, hwRevision, supportedBlocks, sensorInfo, sensor_->controls(), &ipaControls_); if (ret < 0) { LOG(RkISP1, Error) << "IPA initialization failure"; return ret; } ret = loadTuningFile(ipaTuningFile); if (ret < 0) { LOG(RkISP1, Error) << "Failed to load tuning file"; return ret; } return 0; } int RkISP1CameraData::loadTuningFile(const std::string &path) { int ret; if (!pipe()->dewarper_) /* Nothing to do without dewarper */ return 0; LOG(RkISP1, Debug) << "Load tuning file " << path; File file(path); if (!file.open(File::OpenModeFlag::ReadOnly)) { ret = file.error(); LOG(RkISP1, Error) << "Failed to open tuning file " << path << ": " << strerror(-ret); return ret; } std::unique_ptr data = YamlParser::parse(file); if (!data) return -EINVAL; if (!data->contains("modules")) return 0; const auto &modules = (*data)["modules"].asList(); for (const auto &module : modules) { const auto ¶ms = module["Dewarp"]; if (!params) continue; ret = pipe()->dewarper_->init(params); if (ret) return ret; LOG(RkISP1, Info) << "Dw100 dewarper initialized"; canUseDewarper_ = true; return 0; } return 0; } void RkISP1CameraData::paramsComputed(unsigned int frame, unsigned int bytesused) { PipelineHandlerRkISP1 *pipe = RkISP1CameraData::pipe(); RkISP1FrameInfo *info = frameInfo_.find(frame); if (!info) return; info->paramBuffer->_d()->metadata().planes()[0].bytesused = bytesused; int ret = pipe->param_->queueBuffer(info->paramBuffer); if (ret < 0) { LOG(RkISP1, Error) << "Failed to queue parameter buffer: " << strerror(-ret); return; } pipe->stat_->queueBuffer(info->statBuffer); if (info->mainPathBuffer) mainPath_->queueBuffer(info->mainPathBuffer); if (selfPath_ && info->selfPathBuffer) selfPath_->queueBuffer(info->selfPathBuffer); } void RkISP1CameraData::setSensorControls([[maybe_unused]] unsigned int frame, const ControlList &sensorControls) { delayedCtrls_->push(sensorControls); } void RkISP1CameraData::metadataReady(unsigned int frame, const ControlList &metadata) { RkISP1FrameInfo *info = frameInfo_.find(frame); if (!info) return; info->request->_d()->metadata().merge(metadata); info->metadataProcessed = true; pipe()->tryCompleteRequest(info); } /* ----------------------------------------------------------------------------- * Camera Configuration */ namespace { /* Keep in sync with the supported raw formats in rkisp1_path.cpp. */ const std::map rawFormats = { { formats::SBGGR8, MEDIA_BUS_FMT_SBGGR8_1X8 }, { formats::SGBRG8, MEDIA_BUS_FMT_SGBRG8_1X8 }, { formats::SGRBG8, MEDIA_BUS_FMT_SGRBG8_1X8 }, { formats::SRGGB8, MEDIA_BUS_FMT_SRGGB8_1X8 }, { formats::SBGGR10, MEDIA_BUS_FMT_SBGGR10_1X10 }, { formats::SGBRG10, MEDIA_BUS_FMT_SGBRG10_1X10 }, { formats::SGRBG10, MEDIA_BUS_FMT_SGRBG10_1X10 }, { formats::SRGGB10, MEDIA_BUS_FMT_SRGGB10_1X10 }, { formats::SBGGR12, MEDIA_BUS_FMT_SBGGR12_1X12 }, { formats::SGBRG12, MEDIA_BUS_FMT_SGBRG12_1X12 }, { formats::SGRBG12, MEDIA_BUS_FMT_SGRBG12_1X12 }, { formats::SRGGB12, MEDIA_BUS_FMT_SRGGB12_1X12 }, }; } /* namespace */ RkISP1CameraConfiguration::RkISP1CameraConfiguration(Camera *camera, RkISP1CameraData *data) : CameraConfiguration() { camera_ = camera->shared_from_this(); data_ = data; } bool RkISP1CameraConfiguration::fitsAllPaths(const StreamConfiguration &cfg) { const CameraSensor *sensor = data_->sensor_.get(); StreamConfiguration config; config = cfg; if (data_->mainPath_->validate(sensor, sensorConfig, &config) != Valid) return false; config = cfg; if (data_->selfPath_ && data_->selfPath_->validate(sensor, sensorConfig, &config) != Valid) return false; return true; } CameraConfiguration::Status RkISP1CameraConfiguration::validate() { const PipelineHandlerRkISP1 *pipe = data_->pipe(); const CameraSensor *sensor = data_->sensor_.get(); unsigned int pathCount = data_->selfPath_ ? 2 : 1; Status status; if (config_.empty()) return Invalid; status = validateColorSpaces(ColorSpaceFlag::StreamsShareColorSpace); /* * Make sure that if a sensor configuration has been requested it * is valid. */ if (sensorConfig) { if (!sensorConfig->isValid()) { LOG(RkISP1, Error) << "Invalid sensor configuration request"; return Invalid; } unsigned int bitDepth = sensorConfig->bitDepth; if (bitDepth != 8 && bitDepth != 10 && bitDepth != 12) { LOG(RkISP1, Error) << "Invalid sensor configuration bit depth"; return Invalid; } } /* Cap the number of entries to the available streams. */ if (config_.size() > pathCount) { config_.resize(pathCount); status = Adjusted; } /* * Simultaneous capture of raw and processed streams isn't possible. * Let the first stream decide on the type. */ bool isRaw = (PixelFormatInfo::info(config_[0].pixelFormat).colourEncoding == PixelFormatInfo::ColourEncodingRAW); if (isRaw) { if (config_.size() > 1) { config_.resize(1); status = Adjusted; } } else { /* Drop all raw configs. */ for (auto it = config_.begin(); it != config_.end();) { if (PixelFormatInfo::info(it->pixelFormat).colourEncoding == PixelFormatInfo::ColourEncodingRAW) { it = config_.erase(it); status = Adjusted; continue; } ++it; } } /* * If the dewarper supports orientation adjustments, apply that completely * there. Even if the sensor supports flips, it is beneficial to do that * in the dewarper so that lens dewarping happens on the unflipped image */ bool transposeAfterIsp = false; bool useDewarper = (data_->canUseDewarper_ && !isRaw); if (useDewarper) { combinedTransform_ = orientation / data_->sensor_->mountingOrientation(); if (!!(combinedTransform_ & Transform::Transpose)) transposeAfterIsp = true; } else { Orientation requestedOrientation = orientation; combinedTransform_ = data_->sensor_->computeTransform(&orientation); if (orientation != requestedOrientation) status = Adjusted; } /* * If there are more than one stream in the configuration figure out the * order to evaluate the streams. The first stream has the highest * priority but if both main path and self path can satisfy it evaluate * the second stream first as the first stream is guaranteed to work * with whichever path is not used by the second one. */ std::vector order(config_.size()); std::iota(order.begin(), order.end(), 0); if (config_.size() == 2 && fitsAllPaths(config_[0])) std::reverse(order.begin(), order.end()); /* * Validate the configuration against the desired path and, if the * platform supports it, the dewarper. While iterating over the * configurations collect the smallest common sensor format. */ Size accumulatedSensorSize; auto validateConfig = [&](StreamConfiguration &cfg, RkISP1Path *path, Stream *stream, Status expectedStatus) { StreamConfiguration tryCfg = cfg; /* Need to validate the path before the transpose */ if (transposeAfterIsp) tryCfg.size.transpose(); Status ret = path->validate(sensor, sensorConfig, &tryCfg); if (ret == Invalid) return false; if (!useDewarper && (expectedStatus == Valid && ret == Adjusted)) return false; Size sensorSize = tryCfg.size; if (useDewarper) { /* * The dewarper output is independent of the ISP path. * Reset to the originally requested size. */ tryCfg.size = cfg.size; bool adjusted; pipe->dewarper_->validateOutput(&tryCfg, &adjusted, Converter::Alignment::Down); if (expectedStatus == Valid && adjusted) return false; } if (tryCfg.bufferCount < kRkISP1MinBufferCount) { if (expectedStatus == Valid) return false; tryCfg.bufferCount = kRkISP1MinBufferCount; } cfg = tryCfg; cfg.setStream(stream); accumulatedSensorSize = std::max(accumulatedSensorSize, sensorSize); return true; }; bool mainPathAvailable = true; bool selfPathAvailable = data_->selfPath_; RkISP1Path *mainPath = data_->mainPath_; RkISP1Path *selfPath = data_->selfPath_; Stream *mainPathStream = const_cast(&data_->mainPathStream_); Stream *selfPathStream = const_cast(&data_->selfPathStream_); for (unsigned int index : order) { StreamConfiguration &cfg = config_[index]; /* Try to match stream without adjusting configuration. */ if (mainPathAvailable) { if (validateConfig(cfg, mainPath, mainPathStream, Valid)) { mainPathAvailable = false; continue; } } if (selfPathAvailable) { if (validateConfig(cfg, selfPath, selfPathStream, Valid)) { selfPathAvailable = false; continue; } } /* Try to match stream allowing adjusting configuration. */ if (mainPathAvailable) { if (validateConfig(cfg, mainPath, mainPathStream, Adjusted)) { mainPathAvailable = false; status = Adjusted; continue; } } if (selfPathAvailable) { if (validateConfig(cfg, selfPath, selfPathStream, Adjusted)) { selfPathAvailable = false; status = Adjusted; continue; } } /* All paths rejected configuration. */ LOG(RkISP1, Debug) << "Camera configuration not supported " << cfg.toString(); return Invalid; } std::vector mbusCodes; if (isRaw) { mbusCodes = { rawFormats.at(config_[0].pixelFormat) }; } else { std::transform(rawFormats.begin(), rawFormats.end(), std::back_inserter(mbusCodes), [](const auto &value) { return value.second; }); } sensorFormat_ = sensor->getFormat(mbusCodes, accumulatedSensorSize, mainPath->maxResolution()); if (sensorFormat_.size.isNull()) sensorFormat_.size = sensor->resolution(); return status; } /* ----------------------------------------------------------------------------- * Pipeline Operations */ PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager) : PipelineHandler(manager, kRkISP1MaxQueuedRequests), hasSelfPath_(true) { } std::unique_ptr PipelineHandlerRkISP1::generateConfiguration(Camera *camera, Span roles) { RkISP1CameraData *data = cameraData(camera); unsigned int pathCount = data->selfPath_ ? 2 : 1; if (roles.size() > pathCount) { LOG(RkISP1, Error) << "Too many stream roles requested"; return nullptr; } std::unique_ptr config = std::make_unique(camera, data); if (roles.empty()) return config; Transform transform = Transform::Identity; Size previewSize = kRkISP1PreviewSize; bool transposeAfterIsp = false; if (data->canUseDewarper_) { transform = Orientation::Rotate0 / data->sensor_->mountingOrientation(); if (!!(transform & Transform::Transpose)) transposeAfterIsp = true; } /* * In case of a transpose transform we need to create a path for the * transposed size. */ if (transposeAfterIsp) previewSize.transpose(); /* * As the ISP can't output different color spaces for the main and self * path, pick a sensible default color space based on the role of the * first stream and use it for all streams. */ std::optional colorSpace; bool mainPathAvailable = true; for (const StreamRole role : roles) { Size size; switch (role) { case StreamRole::StillCapture: /* JPEG encoders typically expect sYCC. */ if (!colorSpace) colorSpace = ColorSpace::Sycc; size = data->sensor_->resolution(); break; case StreamRole::Viewfinder: /* * sYCC is the YCbCr encoding of sRGB, which is commonly * used by displays. */ if (!colorSpace) colorSpace = ColorSpace::Sycc; size = previewSize; break; case StreamRole::VideoRecording: /* Rec. 709 is a good default for HD video recording. */ if (!colorSpace) colorSpace = ColorSpace::Rec709; size = previewSize; break; case StreamRole::Raw: if (roles.size() > 1) { LOG(RkISP1, Error) << "Can't capture both raw and processed streams"; return nullptr; } colorSpace = ColorSpace::Raw; size = data->sensor_->resolution(); break; default: LOG(RkISP1, Warning) << "Requested stream role not supported: " << role; return nullptr; } /* * Prefer the main path if available, as it supports higher * resolutions. * * \todo Using the main path unconditionally hides support for * RGB (only available on the self path) in the streams formats * exposed to applications. This likely calls for a better API * to expose streams capabilities. */ RkISP1Path *path; if (mainPathAvailable) { path = data->mainPath_; mainPathAvailable = false; } else { path = data->selfPath_; } StreamConfiguration cfg = path->generateConfiguration(data->sensor_.get(), size, role); if (!cfg.pixelFormat.isValid()) return nullptr; if (transposeAfterIsp && role != StreamRole::Raw) cfg.size.transpose(); cfg.colorSpace = colorSpace; cfg.bufferCount = kRkISP1MinBufferCount; config->addConfiguration(cfg); } config->validate(); return config; } int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c) { RkISP1CameraConfiguration *config = static_cast(c); RkISP1CameraData *data = cameraData(camera); CameraSensor *sensor = data->sensor_.get(); int ret; ret = initLinks(camera, *config); if (ret) return ret; const PixelFormat &streamFormat = config->at(0).pixelFormat; const PixelFormatInfo &info = PixelFormatInfo::info(streamFormat); isRaw_ = info.colourEncoding == PixelFormatInfo::ColourEncodingRAW; data->usesDewarper_ = data->canUseDewarper_ && !isRaw_; Transform transform = config->combinedTransform(); bool transposeAfterIsp = false; if (data->usesDewarper_) { if (!!(transform & Transform::Transpose)) transposeAfterIsp = true; transform = Transform::Identity; } /* * Configure the format on the sensor output and propagate it through * the pipeline. */ V4L2SubdeviceFormat format = config->sensorFormat(); LOG(RkISP1, Debug) << "Configuring sensor with " << format; if (config->sensorConfig) ret = sensor->applyConfiguration(*config->sensorConfig, transform, &format); else ret = sensor->setFormat(&format, transform); if (ret < 0) return ret; LOG(RkISP1, Debug) << "Sensor configured with " << format; /* Propagate format through the internal media pipeline up to the ISP */ ret = data->pipe_.configure(sensor, &format); if (ret < 0) return ret; LOG(RkISP1, Debug) << "Configuring ISP with : " << format; ret = isp_->setFormat(0, &format); if (ret < 0) return ret; Rectangle inputCrop(0, 0, format.size); ret = isp_->setSelection(0, V4L2_SEL_TGT_CROP, &inputCrop); if (ret < 0) return ret; LOG(RkISP1, Debug) << "ISP input pad configured with " << format << " crop " << inputCrop; Rectangle outputCrop = inputCrop; /* YUYV8_2X8 is required on the ISP source path pad for YUV output. */ if (!isRaw_) format.code = MEDIA_BUS_FMT_YUYV8_2X8; /* * On devices without DUAL_CROP (like the imx8mp) cropping needs to be * done on the ISP/IS output. * * If the dewarper is used, the cropping shall be done by the dewarper. */ if (media_->hwRevision() == RKISP1_V_IMX8MP) { /* imx8mp has only a single path. */ const auto &cfg = config->at(0); /* * If the dewarper is used, all cropping including aspect ratio * preservation shall be done there. To ensure that the output * format provided by the ISP is supported by the dewarper, a * minimal crop still needs to be applied on the ISP output. * * \todo It might be possible to allocate bigger buffers * (aligned to 8 pixels) with a stride matching format.size for * the ISP. The not-filled border could later be ignored by the * dewarper. This way we could skip the minimal crop here and * the MaximumScalerCrop would always match the isp output. */ Size ispCrop; if (data->usesDewarper_) ispCrop = dewarper_->adjustInputSize(cfg.pixelFormat, format.size); else ispCrop = format.size.boundedToAspectRatio(cfg.size) .alignedUpTo(2, 2); outputCrop = ispCrop.centeredTo(Rectangle(format.size).center()); format.size = ispCrop; } LOG(RkISP1, Debug) << "Configuring ISP output pad with " << format << " crop " << outputCrop; ret = isp_->setSelection(2, V4L2_SEL_TGT_CROP, &outputCrop); if (ret < 0) return ret; format.colorSpace = config->at(0).colorSpace; ret = isp_->setFormat(2, &format); if (ret < 0) return ret; LOG(RkISP1, Debug) << "ISP output pad configured with " << format << " crop " << outputCrop; IPACameraSensorInfo sensorInfo; ret = data->sensor_->sensorInfo(&sensorInfo); if (ret) return ret; /* Apply the actual sensor crop for proper dewarp map calculation. */ Rectangle sensorCrop = outputCrop.transformedBetween( inputCrop, sensorInfo.analogCrop); if (data->usesDewarper_) dewarper_->setSensorCrop(sensorCrop); data->properties_.set(properties::ScalerCropMaximum, sensorCrop); std::map streamConfig; std::vector> outputCfgs; for (const StreamConfiguration &cfg : *config) { if (cfg.stream() == &data->mainPathStream_) { /* * To allow for digital zoom, scaling down should happen * in the dewarper, instead of the resizer. Configure * the isp output to the same size as the sensor output. */ StreamConfiguration ispCfg = cfg; if (data->usesDewarper_) { outputCfgs.push_back(const_cast(cfg)); ispCfg.bufferCount = kRkISP1MinBufferCount; ispCfg.size = format.size; ispCfg.stride = PixelFormatInfo::info(ispCfg.pixelFormat) .stride(ispCfg.size.width, 0); ret = dewarper_->configure(ispCfg, outputCfgs); if (ret) return ret; dewarper_->setTransform(cfg.stream(), config->combinedTransform()); /* * Apply a default scaler crop that keeps the * aspect ratio. */ Size size = cfg.size; if (transposeAfterIsp) size.transpose(); size = sensorCrop.size().boundedToAspectRatio(size); ControlList ctrls; ctrls.set(controls::ScalerCrop, size.centeredTo(sensorCrop.center())); dewarper_->setControls(cfg.stream(), ctrls); } ret = mainPath_.configure(ispCfg, format); streamConfig[0] = IPAStream(cfg.pixelFormat, cfg.size); } else if (hasSelfPath_) { ret = selfPath_.configure(cfg, format); streamConfig[1] = IPAStream(cfg.pixelFormat, cfg.size); } else { return -ENODEV; } if (ret) return ret; } V4L2DeviceFormat paramFormat; paramFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_EXT_PARAMS); ret = param_->setFormat(¶mFormat); if (ret) return ret; V4L2DeviceFormat statFormat; statFormat.fourcc = V4L2PixelFormat(V4L2_META_FMT_RK_ISP1_STAT_3A); ret = stat_->setFormat(&statFormat); if (ret) return ret; /* Inform IPA of stream configuration and sensor controls. */ ipa::rkisp1::IPAConfigInfo ipaConfig{ sensorInfo, data->sensor_->controls(), paramFormat.fourcc }; ret = data->ipa_->configure(ipaConfig, streamConfig, &data->ipaControls_); if (ret) { LOG(RkISP1, Error) << "failed configuring IPA (" << ret << ")"; return ret; } return updateControls(data); } int PipelineHandlerRkISP1::exportFrameBuffers([[maybe_unused]] Camera *camera, Stream *stream, std::vector> *buffers) { RkISP1CameraData *data = cameraData(camera); unsigned int count = stream->configuration().bufferCount; if (stream == &data->mainPathStream_) { /* * Currently, i.MX8MP is the only platform with DW100 dewarper. * It has mainpath and no self path. Hence, export buffers from * dewarper just for the main path stream, for now. */ if (data->usesDewarper_) return dewarper_->exportBuffers(&data->mainPathStream_, count, buffers); else return mainPath_.exportBuffers(count, buffers); } else if (hasSelfPath_ && stream == &data->selfPathStream_) { return selfPath_.exportBuffers(count, buffers); } return -EINVAL; } int PipelineHandlerRkISP1::allocateBuffers(Camera *camera) { RkISP1CameraData *data = cameraData(camera); unsigned int ipaBufferId = 1; int ret; auto errorCleanup = utils::scope_exit{ [&]() { paramBuffers_.clear(); statBuffers_.clear(); mainPathBuffers_.clear(); } }; if (!isRaw_) { ret = param_->allocateBuffers(kRkISP1MinBufferCount, ¶mBuffers_); if (ret < 0) return ret; ret = stat_->allocateBuffers(kRkISP1MinBufferCount, &statBuffers_); if (ret < 0) return ret; } /* If the dewarper is being used, allocate internal buffers for ISP. */ if (data->usesDewarper_) { ret = mainPath_.exportBuffers(kRkISP1MinBufferCount, &mainPathBuffers_); if (ret < 0) return ret; for (std::unique_ptr &buffer : mainPathBuffers_) availableMainPathBuffers_.push(buffer.get()); } auto pushBuffers = [&](const std::vector> &buffers, std::queue &queue) { for (const std::unique_ptr &buffer : buffers) { Span planes = buffer->planes(); buffer->setCookie(ipaBufferId++); data->ipaBuffers_.emplace_back(buffer->cookie(), std::vector{ planes.begin(), planes.end() }); queue.push(buffer.get()); } }; pushBuffers(paramBuffers_, availableParamBuffers_); pushBuffers(statBuffers_, availableStatBuffers_); data->ipa_->mapBuffers(data->ipaBuffers_); errorCleanup.release(); return 0; } int PipelineHandlerRkISP1::freeBuffers(Camera *camera) { RkISP1CameraData *data = cameraData(camera); while (!availableStatBuffers_.empty()) availableStatBuffers_.pop(); while (!availableParamBuffers_.empty()) availableParamBuffers_.pop(); while (!availableMainPathBuffers_.empty()) availableMainPathBuffers_.pop(); paramBuffers_.clear(); statBuffers_.clear(); mainPathBuffers_.clear(); std::vector ids; for (IPABuffer &ipabuf : data->ipaBuffers_) ids.push_back(ipabuf.id); data->ipa_->unmapBuffers(ids); data->ipaBuffers_.clear(); if (param_->releaseBuffers()) LOG(RkISP1, Error) << "Failed to release parameters buffers"; if (stat_->releaseBuffers()) LOG(RkISP1, Error) << "Failed to release stat buffers"; return 0; } int PipelineHandlerRkISP1::start(Camera *camera, [[maybe_unused]] const ControlList *controls) { RkISP1CameraData *data = cameraData(camera); utils::ScopeExitActions actions; int ret; /* Allocate buffers for internal pipeline usage. */ ret = allocateBuffers(camera); if (ret) return ret; actions += [&]() { freeBuffers(camera); }; ret = data->ipa_->start(); if (ret) { LOG(RkISP1, Error) << "Failed to start IPA " << camera->id(); return ret; } actions += [&]() { data->ipa_->stop(); }; data->frame_ = 0; if (!isRaw_) { ret = param_->streamOn(); if (ret) { LOG(RkISP1, Error) << "Failed to start parameters " << camera->id(); return ret; } actions += [&]() { param_->streamOff(); }; ret = stat_->streamOn(); if (ret) { LOG(RkISP1, Error) << "Failed to start statistics " << camera->id(); return ret; } actions += [&]() { stat_->streamOff(); }; if (data->usesDewarper_) { if (controls) dewarper_->setControls(&data->mainPathStream_, *controls); ret = dewarper_->start(); if (ret) { LOG(RkISP1, Error) << "Failed to start dewarper"; return ret; } actions += [&]() { dewarper_->stop(); }; } } if (data->mainPath_->isEnabled()) { ret = mainPath_.start(data->mainPathStream_.configuration().bufferCount); if (ret) return ret; actions += [&]() { mainPath_.stop(); }; } if (hasSelfPath_ && data->selfPath_->isEnabled()) { ret = selfPath_.start(data->selfPathStream_.configuration().bufferCount); if (ret) return ret; } isp_->setFrameStartEnabled(true); activeCamera_ = camera; actions.release(); return 0; } void PipelineHandlerRkISP1::stopDevice(Camera *camera) { RkISP1CameraData *data = cameraData(camera); int ret; isp_->setFrameStartEnabled(false); data->ipa_->stop(); if (hasSelfPath_) selfPath_.stop(); mainPath_.stop(); if (!isRaw_) { ret = stat_->streamOff(); if (ret) LOG(RkISP1, Warning) << "Failed to stop statistics for " << camera->id(); ret = param_->streamOff(); if (ret) LOG(RkISP1, Warning) << "Failed to stop parameters for " << camera->id(); if (data->usesDewarper_) dewarper_->stop(); } ASSERT(data->queuedRequests_.empty()); data->frameInfo_.clear(); freeBuffers(camera); activeCamera_ = nullptr; } int PipelineHandlerRkISP1::queueRequestDevice(Camera *camera, Request *request) { RkISP1CameraData *data = cameraData(camera); RkISP1FrameInfo *info = data->frameInfo_.create(data, request, isRaw_); if (!info) return -ENOENT; data->ipa_->queueRequest(data->frame_, request->controls()); if (isRaw_) { if (info->mainPathBuffer) data->mainPath_->queueBuffer(info->mainPathBuffer); if (data->selfPath_ && info->selfPathBuffer) data->selfPath_->queueBuffer(info->selfPathBuffer); } else { data->ipa_->computeParams(data->frame_, info->paramBuffer->cookie()); } data->frame_++; return 0; } /* ----------------------------------------------------------------------------- * Match and Setup */ int PipelineHandlerRkISP1::initLinks(Camera *camera, const RkISP1CameraConfiguration &config) { RkISP1CameraData *data = cameraData(camera); int ret; ret = media_->disableLinks(); if (ret < 0) return ret; /* * Configure the sensor links: enable the links corresponding to this * pipeline all the way up to the ISP, through any connected CSI receiver. */ ret = data->pipe_.initLinks(); if (ret) { LOG(RkISP1, Error) << "Failed to set up pipe links"; return ret; } /* Configure the paths after the ISP */ for (const StreamConfiguration &cfg : config) { if (cfg.stream() == &data->mainPathStream_) ret = data->mainPath_->setEnabled(true); else if (hasSelfPath_ && cfg.stream() == &data->selfPathStream_) ret = data->selfPath_->setEnabled(true); else return -EINVAL; if (ret < 0) return ret; } return 0; } /** * \brief Update the camera controls * \param[in] data The camera data * * Compute the camera controls by calculating controls which the pipeline * is reponsible for and merge them with the controls computed by the IPA. * * This function needs data->ipaControls_ to be refreshed when a new * configuration is applied to the camera by the IPA configure() function. * * Always call this function after IPA configure() to make sure to have a * properly refreshed IPA controls list. * * \return 0 on success or a negative error code otherwise */ int PipelineHandlerRkISP1::updateControls(RkISP1CameraData *data) { ControlInfoMap::Map controls; if (data->usesDewarper_) dewarper_->updateControlInfos(&data->mainPathStream_, controls); /* Add the IPA registered controls to list of camera controls. */ for (const auto &ipaControl : data->ipaControls_) controls[ipaControl.first] = ipaControl.second; data->controlInfo_ = ControlInfoMap(std::move(controls), controls::controls); return 0; } /* * By default we assume all the blocks that were included in the first * extensible parameters series are available. That is the lower 20bits. */ const uint32_t kDefaultExtParamsBlocks = 0xfffff; int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor) { int ret; std::unique_ptr data = std::make_unique(this, &mainPath_, hasSelfPath_ ? &selfPath_ : nullptr); /* Identify the pipeline path between the sensor and the rkisp1_isp */ ret = data->pipe_.init(sensor, "rkisp1_isp"); if (ret) { LOG(RkISP1, Error) << "Failed to identify path from sensor to sink"; return ret; } data->sensor_ = CameraSensorFactoryBase::create(sensor); if (!data->sensor_) return -ENODEV; /* Initialize the camera properties. */ data->properties_ = data->sensor_->properties(); const CameraSensorProperties::SensorDelays &delays = data->sensor_->sensorDelays(); std::unordered_map params = { { V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } }, { V4L2_CID_EXPOSURE, { delays.exposureDelay, false } }, { V4L2_CID_VBLANK, { delays.vblankDelay, true } }, }; data->delayedCtrls_ = std::make_unique(data->sensor_->device(), params); isp_->frameStart.connect(data->delayedCtrls_.get(), &DelayedControls::applyControls); uint32_t supportedBlocks = kDefaultExtParamsBlocks; auto &controls = param_->controls(); if (controls.find(RKISP1_CID_SUPPORTED_PARAMS_BLOCKS) != controls.end()) { auto list = param_->getControls({ { RKISP1_CID_SUPPORTED_PARAMS_BLOCKS } }); if (!list.empty()) supportedBlocks = static_cast( list.get(RKISP1_CID_SUPPORTED_PARAMS_BLOCKS) .get()); } else { LOG(RkISP1, Error) << "Failed to query supported params blocks. Falling back to defaults."; } ret = data->loadIPA(media_->hwRevision(), supportedBlocks); if (ret) return ret; updateControls(data.get()); std::set streams{ &data->mainPathStream_, &data->selfPathStream_, }; const std::string &id = data->sensor_->id(); std::shared_ptr camera = Camera::create(std::move(data), id, streams); registerCamera(std::move(camera)); return 0; } bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator) { DeviceMatch dm("rkisp1"); dm.add("rkisp1_isp"); dm.add("rkisp1_resizer_mainpath"); dm.add("rkisp1_mainpath"); dm.add("rkisp1_stats"); dm.add("rkisp1_params"); media_ = acquireMediaDevice(enumerator, dm); if (!media_) return false; if (!media_->hwRevision()) { LOG(RkISP1, Error) << "The rkisp1 driver is too old, v5.11 or newer is required"; return false; } hasSelfPath_ = !!media_->getEntityByName("rkisp1_selfpath"); /* Create the V4L2 subdevices we will need. */ isp_ = V4L2Subdevice::fromEntityName(media_.get(), "rkisp1_isp"); if (isp_->open() < 0) return false; /* Locate and open the stats and params video nodes. */ stat_ = V4L2VideoDevice::fromEntityName(media_.get(), "rkisp1_stats"); if (stat_->open() < 0) return false; param_ = V4L2VideoDevice::fromEntityName(media_.get(), "rkisp1_params"); if (param_->open() < 0) return false; /* Locate and open the ISP main and self paths. */ if (!mainPath_.init(media_)) return false; if (hasSelfPath_ && !selfPath_.init(media_)) return false; mainPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::imageBufferReady); if (hasSelfPath_) selfPath_.bufferReady().connect(this, &PipelineHandlerRkISP1::imageBufferReady); stat_->bufferReady.connect(this, &PipelineHandlerRkISP1::statBufferReady); param_->bufferReady.connect(this, &PipelineHandlerRkISP1::paramBufferReady); dewarper_ = ConverterDW100Module::createModule(enumerator); if (dewarper_) { dewarper_->outputBufferReady.connect( this, &PipelineHandlerRkISP1::dewarpBufferReady); LOG(RkISP1, Debug) << "Found DW100 dewarper"; } /* * Enumerate all sensors connected to the ISP and create one * camera instance for each of them. */ bool registered = false; for (MediaEntity *entity : media_->locateEntities(MEDIA_ENT_F_CAM_SENSOR)) { LOG(RkISP1, Debug) << "Identified " << entity->name(); if (!createCamera(entity)) registered = true; } return registered; } /* ----------------------------------------------------------------------------- * Buffer Handling */ void PipelineHandlerRkISP1::tryCompleteRequest(RkISP1FrameInfo *info) { RkISP1CameraData *data = cameraData(activeCamera_); Request *request = info->request; if (request->hasPendingBuffers()) return; if (!info->metadataProcessed) return; if (!isRaw_ && !info->paramDequeued) return; data->frameInfo_.destroy(info->frame); completeRequest(request); } void PipelineHandlerRkISP1::cancelDewarpRequest(RkISP1FrameInfo *info) { RkISP1CameraData *data = cameraData(activeCamera_); Request *request = info->request; /* * i.MX8MP is the only known platform with dewarper. It has * no self path. Hence, only main path buffer completion is * required. * * Also, we cannot completeBuffer(request, buffer) as buffer * here, is an internal buffer (between ISP and dewarper) and * is not associated to the any specific request. The request * buffer associated with main path stream is the one that * is required to be completed (not the internal buffer). */ for (auto [stream, buffer] : request->buffers()) { if (stream == &data->mainPathStream_) { buffer->_d()->cancel(); completeBuffer(request, buffer); } } tryCompleteRequest(info); } void PipelineHandlerRkISP1::imageBufferReady(FrameBuffer *buffer) { ASSERT(activeCamera_); RkISP1CameraData *data = cameraData(activeCamera_); RkISP1FrameInfo *info = data->frameInfo_.find(buffer); if (!info) return; const FrameMetadata &metadata = buffer->metadata(); Request *request = info->request; if (metadata.status != FrameMetadata::FrameCancelled) { /* * Record the sensor's timestamp in the request metadata. * * \todo The sensor timestamp should be better estimated by connecting * to the V4L2Device::frameStart signal. */ request->_d()->metadata().set(controls::SensorTimestamp, metadata.timestamp); if (isRaw_) { const ControlList &ctrls = data->delayedCtrls_->get(metadata.sequence); data->ipa_->processStats(info->frame, 0, ctrls); } } else { if (isRaw_) info->metadataProcessed = true; } if (!data->usesDewarper_) { completeBuffer(request, buffer); tryCompleteRequest(info); return; } /* Do not queue cancelled frames to dewarper. */ if (metadata.status == FrameMetadata::FrameCancelled) { cancelDewarpRequest(info); return; } dewarper_->setControls(&data->mainPathStream_, request->controls()); /* * Queue input and output buffers to the dewarper. The output * buffers for the dewarper are the buffers of the request, supplied * by the application. */ int ret = dewarper_->queueBuffers(buffer, request->buffers()); if (ret < 0) { LOG(RkISP1, Error) << "Failed to queue buffers to dewarper: -" << strerror(-ret); cancelDewarpRequest(info); return; } dewarper_->populateMetadata(&data->mainPathStream_, request->_d()->metadata()); } void PipelineHandlerRkISP1::dewarpBufferReady(FrameBuffer *buffer) { ASSERT(activeCamera_); RkISP1CameraData *data = cameraData(activeCamera_); Request *request = buffer->request(); RkISP1FrameInfo *info = data->frameInfo_.find(buffer->request()); if (!info) return; completeBuffer(request, buffer); tryCompleteRequest(info); } void PipelineHandlerRkISP1::paramBufferReady(FrameBuffer *buffer) { ASSERT(activeCamera_); RkISP1CameraData *data = cameraData(activeCamera_); RkISP1FrameInfo *info = data->frameInfo_.find(buffer); if (!info) return; info->paramDequeued = true; tryCompleteRequest(info); } void PipelineHandlerRkISP1::statBufferReady(FrameBuffer *buffer) { ASSERT(activeCamera_); RkISP1CameraData *data = cameraData(activeCamera_); RkISP1FrameInfo *info = data->frameInfo_.find(buffer); if (!info) return; if (buffer->metadata().status == FrameMetadata::FrameCancelled) { info->metadataProcessed = true; tryCompleteRequest(info); return; } if (data->frame_ <= buffer->metadata().sequence) data->frame_ = buffer->metadata().sequence + 1; data->ipa_->processStats(info->frame, info->statBuffer->cookie(), data->delayedCtrls_->get(buffer->metadata().sequence)); } REGISTER_PIPELINE_HANDLER(PipelineHandlerRkISP1, "rkisp1") } /* namespace libcamera */