/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2020, Raspberry Pi (Trading) Ltd. * * delayed_controls.h - Helper to deal with controls that take effect with a delay */ #include "libcamera/internal/delayed_controls.h" #include #include #include "libcamera/internal/v4l2_device.h" /** * \file delayed_controls.h * \brief Helper to deal with controls that take effect with a delay */ namespace libcamera { LOG_DEFINE_CATEGORY(DelayedControls) /** * \class DelayedControls * \brief Helper to deal with controls that take effect with a delay * * Some sensor controls take effect with a delay as the sensor needs time to * adjust, for example exposure and analog gain. This is a helper class to deal * with such controls and the intended users are pipeline handlers. * * The idea is to extend the concept of the buffer depth of a pipeline the * application needs to maintain to also cover controls. Just as with buffer * depth if the application keeps the number of requests queued above the * control depth the controls are guaranteed to take effect for the correct * request. The control depth is determined by the control with the greatest * delay. */ /** * \struct DelayedControls::ControlParams * \brief Parameters associated with controls handled by the \a DelayedControls * helper class * * \var ControlParams::delay * \brief Frame delay from setting the control on a sensor device to when it is * consumed during framing. * * \var ControlParams::priorityWrite * \brief Flag to indicate that this control must be applied ahead of, and * separately from the other controls. * * Typically set for the \a V4L2_CID_VBLANK control so that the device driver * does not reject \a V4L2_CID_EXPOSURE control values that may be outside of * the existing vertical blanking specified bounds, but are within the new * blanking bounds. */ /** * \brief Construct a DelayedControls instance * \param[in] device The V4L2 device the controls have to be applied to * \param[in] controlParams Map of the numerical V4L2 control ids to their * associated control parameters. * * The control parameters comprise of delays (in frames) and a priority write * flag. If this flag is set, the relevant control is written separately from, * and ahead of the rest of the batched controls. * * Only controls specified in \a controlParams are handled. If it's desired to * mix delayed controls and controls that take effect immediately the immediate * controls must be listed in the \a controlParams map with a delay value of 0. */ DelayedControls::DelayedControls(V4L2Device *device, const std::unordered_map &controlParams) : device_(device), maxDelay_(0) { const ControlInfoMap &controls = device_->controls(); /* * Create a map of control ids to delays for controls exposed by the * device. */ for (auto const ¶m : controlParams) { auto it = controls.find(param.first); if (it == controls.end()) { LOG(DelayedControls, Error) << "Delay request for control id " << utils::hex(param.first) << " but control is not exposed by device " << device_->deviceNode(); continue; } const ControlId *id = it->first; controlParams_[id] = param.second; LOG(DelayedControls, Debug) << "Set a delay of " << controlParams_[id].delay << " and priority write flag " << controlParams_[id].priorityWrite << " for " << id->name(); maxDelay_ = std::max(maxDelay_, controlParams_[id].delay); } reset(); } /** * \brief Reset state machine * * Resets the state machine to a starting position based on control values * retrieved from the device. */ void DelayedControls::reset() { running_ = false; firstSequence_ = 0; queueCount_ = 1; writeCount_ = 0; /* Retrieve control as reported by the device. */ std::vector ids; for (auto const ¶m : controlParams_) ids.push_back(param.first->id()); ControlList controls = device_->getControls(ids); /* Seed the control queue with the controls reported by the device. */ values_.clear(); for (const auto &ctrl : controls) { const ControlId *id = device_->controls().idmap().at(ctrl.first); /* * Do not mark this control value as updated, it does not need * to be written to to device on startup. */ values_[id][0] = Info(ctrl.second, false); } } /** * \brief Push a set of controls on the queue * \param[in] controls List of controls to add to the device queue * * Push a set of controls to the control queue. This increases the control queue * depth by one. * * \returns true if \a controls are accepted, or false otherwise */ bool DelayedControls::push(const ControlList &controls) { /* Copy state from previous frame. */ for (auto &ctrl : values_) { Info &info = ctrl.second[queueCount_]; info = values_[ctrl.first][queueCount_ - 1]; info.updated = false; } /* Update with new controls. */ const ControlIdMap &idmap = device_->controls().idmap(); for (const auto &control : controls) { const auto &it = idmap.find(control.first); if (it == idmap.end()) { LOG(DelayedControls, Warning) << "Unknown control " << control.first; return false; } const ControlId *id = it->second; if (controlParams_.find(id) == controlParams_.end()) return false; Info &info = values_[id][queueCount_]; info = Info(control.second); LOG(DelayedControls, Debug) << "Queuing " << id->name() << " to " << info.toString() << " at index " << queueCount_; } queueCount_++; return true; } /** * \brief Read back controls in effect at a sequence number * \param[in] sequence The sequence number to get controls for * * Read back what controls where in effect at a specific sequence number. The * history is a ring buffer of 16 entries where new and old values coexist. It's * the callers responsibility to not read too old sequence numbers that have been * pushed out of the history. * * Historic values are evicted by pushing new values onto the queue using * push(). The max history from the current sequence number that yields valid * values are thus 16 minus number of controls pushed. * * \return The controls at \a sequence number */ ControlList DelayedControls::get(uint32_t sequence) { uint32_t adjustedSeq = sequence - firstSequence_; unsigned int index = std::max(0, adjustedSeq - maxDelay_); ControlList out(device_->controls()); for (const auto &ctrl : values_) { const ControlId *id = ctrl.first; const Info &info = ctrl.second[index]; out.set(id->id(), info); LOG(DelayedControls, Debug) << "Reading " << id->name() << " to " << info.toString() << " at index " << index; } return out; } /** * \brief Inform DelayedControls of the start of a new frame * \param[in] sequence Sequence number of the frame that started * * Inform the state machine that a new frame has started and of its sequence * number. Any user of these helpers is responsible to inform the helper about * the start of any frame. This can be connected with ease to the start of a * exposure (SOE) V4L2 event. */ void DelayedControls::applyControls(uint32_t sequence) { LOG(DelayedControls, Debug) << "frame " << sequence << " started"; if (!running_) { firstSequence_ = sequence; running_ = true; } /* * Create control list peeking ahead in the value queue to ensure * values are set in time to satisfy the sensor delay. */ ControlList out(device_->controls()); for (auto &ctrl : values_) { const ControlId *id = ctrl.first; unsigned int delayDiff = maxDelay_ - controlParams_[id].delay; unsigned int index = std::max(0, writeCount_ - delayDiff); Info &info = ctrl.second[index]; if (info.updated) { if (controlParams_[id].priorityWrite) { /* * This control must be written now, it could * affect validity of the other controls. */ ControlList priority(device_->controls()); priority.set(id->id(), info); device_->setControls(&priority); } else { /* * Batch up the list of controls and write them * at the end of the function. */ out.set(id->id(), info); } LOG(DelayedControls, Debug) << "Setting " << id->name() << " to " << info.toString() << " at index " << index; /* Done with this update, so mark as completed. */ info.updated = false; } } writeCount_++; while (writeCount_ > queueCount_) { LOG(DelayedControls, Debug) << "Queue is empty, auto queue no-op."; push({}); } device_->setControls(&out); } } /* namespace libcamera */