/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* * Copyright (C) 2018, Google Inc. * * vimc.cpp - Pipeline handler for the vimc device */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libcamera/internal/camera_sensor.h" #include "libcamera/internal/device_enumerator.h" #include "libcamera/internal/ipa_manager.h" #include "libcamera/internal/log.h" #include "libcamera/internal/media_device.h" #include "libcamera/internal/pipeline_handler.h" #include "libcamera/internal/utils.h" #include "libcamera/internal/v4l2_controls.h" #include "libcamera/internal/v4l2_subdevice.h" #include "libcamera/internal/v4l2_videodevice.h" namespace libcamera { LOG_DEFINE_CATEGORY(VIMC) class VimcCameraData : public CameraData { public: VimcCameraData(PipelineHandler *pipe, MediaDevice *media) : CameraData(pipe), media_(media), sensor_(nullptr), debayer_(nullptr), scaler_(nullptr), video_(nullptr), raw_(nullptr) { } ~VimcCameraData() { delete sensor_; delete debayer_; delete scaler_; delete video_; delete raw_; } int init(); void bufferReady(FrameBuffer *buffer); MediaDevice *media_; CameraSensor *sensor_; V4L2Subdevice *debayer_; V4L2Subdevice *scaler_; V4L2VideoDevice *video_; V4L2VideoDevice *raw_; Stream stream_; }; class VimcCameraConfiguration : public CameraConfiguration { public: VimcCameraConfiguration(); Status validate() override; }; class PipelineHandlerVimc : public PipelineHandler { public: PipelineHandlerVimc(CameraManager *manager); CameraConfiguration *generateConfiguration(Camera *camera, const StreamRoles &roles) override; int configure(Camera *camera, CameraConfiguration *config) override; int exportFrameBuffers(Camera *camera, Stream *stream, std::vector> *buffers) override; int start(Camera *camera) override; void stop(Camera *camera) override; int queueRequestDevice(Camera *camera, Request *request) override; bool match(DeviceEnumerator *enumerator) override; private: int processControls(VimcCameraData *data, Request *request); VimcCameraData *cameraData(const Camera *camera) { return static_cast( PipelineHandler::cameraData(camera)); } }; namespace { static const std::array pixelformats{ PixelFormat(DRM_FORMAT_RGB888), PixelFormat(DRM_FORMAT_BGR888), PixelFormat(DRM_FORMAT_BGRA8888), }; } /* namespace */ VimcCameraConfiguration::VimcCameraConfiguration() : CameraConfiguration() { } CameraConfiguration::Status VimcCameraConfiguration::validate() { Status status = Valid; if (config_.empty()) return Invalid; /* Cap the number of entries to the available streams. */ if (config_.size() > 1) { config_.resize(1); status = Adjusted; } StreamConfiguration &cfg = config_[0]; /* Adjust the pixel format. */ if (std::find(pixelformats.begin(), pixelformats.end(), cfg.pixelFormat) == pixelformats.end()) { LOG(VIMC, Debug) << "Adjusting format to RGB24"; cfg.pixelFormat = PixelFormat(DRM_FORMAT_BGR888); status = Adjusted; } /* Clamp the size based on the device limits. */ const Size size = cfg.size; /* The scaler hardcodes a x3 scale-up ratio. */ cfg.size.width = std::max(48U, std::min(4096U, cfg.size.width)); cfg.size.height = std::max(48U, std::min(2160U, cfg.size.height)); cfg.size.width -= cfg.size.width % 3; cfg.size.height -= cfg.size.height % 3; if (cfg.size != size) { LOG(VIMC, Debug) << "Adjusting size to " << cfg.size.toString(); status = Adjusted; } cfg.bufferCount = 4; return status; } PipelineHandlerVimc::PipelineHandlerVimc(CameraManager *manager) : PipelineHandler(manager) { } CameraConfiguration *PipelineHandlerVimc::generateConfiguration(Camera *camera, const StreamRoles &roles) { CameraConfiguration *config = new VimcCameraConfiguration(); if (roles.empty()) return config; std::map> formats; for (PixelFormat pixelformat : pixelformats) { /* The scaler hardcodes a x3 scale-up ratio. */ std::vector sizes{ SizeRange{ { 48, 48 }, { 4096, 2160 } } }; formats[pixelformat] = sizes; } StreamConfiguration cfg(formats); cfg.pixelFormat = PixelFormat(DRM_FORMAT_BGR888); cfg.size = { 1920, 1080 }; cfg.bufferCount = 4; config->addConfiguration(cfg); config->validate(); return config; } int PipelineHandlerVimc::configure(Camera *camera, CameraConfiguration *config) { VimcCameraData *data = cameraData(camera); StreamConfiguration &cfg = config->at(0); int ret; /* The scaler hardcodes a x3 scale-up ratio. */ V4L2SubdeviceFormat subformat = {}; subformat.mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8; subformat.size = { cfg.size.width / 3, cfg.size.height / 3 }; ret = data->sensor_->setFormat(&subformat); if (ret) return ret; ret = data->debayer_->setFormat(0, &subformat); if (ret) return ret; subformat.mbus_code = MEDIA_BUS_FMT_RGB888_1X24; ret = data->debayer_->setFormat(1, &subformat); if (ret) return ret; ret = data->scaler_->setFormat(0, &subformat); if (ret) return ret; if (data->media_->version() >= KERNEL_VERSION(5, 6, 0)) { Rectangle crop = { .x = 0, .y = 0, .width = subformat.size.width, .height = subformat.size.height, }; ret = data->scaler_->setSelection(0, V4L2_SEL_TGT_CROP, &crop); if (ret) return ret; } subformat.size = cfg.size; ret = data->scaler_->setFormat(1, &subformat); if (ret) return ret; V4L2DeviceFormat format = {}; format.fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat); format.size = cfg.size; ret = data->video_->setFormat(&format); if (ret) return ret; if (format.size != cfg.size || format.fourcc != data->video_->toV4L2PixelFormat(cfg.pixelFormat)) return -EINVAL; /* * Format has to be set on the raw capture video node, otherwise the * vimc driver will fail pipeline validation. */ format.fourcc = V4L2PixelFormat(V4L2_PIX_FMT_SGRBG8); format.size = { cfg.size.width / 3, cfg.size.height / 3 }; ret = data->raw_->setFormat(&format); if (ret) return ret; cfg.setStream(&data->stream_); cfg.stride = format.planes[0].bpl; return 0; } int PipelineHandlerVimc::exportFrameBuffers(Camera *camera, Stream *stream, std::vector> *buffers) { VimcCameraData *data = cameraData(camera); unsigned int count = stream->configuration().bufferCount; return data->video_->exportBuffers(count, buffers); } int PipelineHandlerVimc::start(Camera *camera) { VimcCameraData *data = cameraData(camera); unsigned int count = data->stream_.configuration().bufferCount; int ret = data->video_->importBuffers(count); if (ret < 0) return ret; ret = data->ipa_->start(); if (ret) { data->video_->releaseBuffers(); return ret; } ret = data->video_->streamOn(); if (ret < 0) { data->ipa_->stop(); data->video_->releaseBuffers(); return ret; } return 0; } void PipelineHandlerVimc::stop(Camera *camera) { VimcCameraData *data = cameraData(camera); data->video_->streamOff(); data->ipa_->stop(); data->video_->releaseBuffers(); } int PipelineHandlerVimc::processControls(VimcCameraData *data, Request *request) { ControlList controls(data->sensor_->controls()); for (auto it : request->controls()) { unsigned int id = it.first; unsigned int offset; uint32_t cid; if (id == controls::Brightness) { cid = V4L2_CID_BRIGHTNESS; offset = 128; } else if (id == controls::Contrast) { cid = V4L2_CID_CONTRAST; offset = 0; } else if (id == controls::Saturation) { cid = V4L2_CID_SATURATION; offset = 0; } else { continue; } int32_t value = lroundf(it.second.get() * 128 + offset); controls.set(cid, utils::clamp(value, 0, 255)); } for (const auto &ctrl : controls) LOG(VIMC, Debug) << "Setting control " << utils::hex(ctrl.first) << " to " << ctrl.second.toString(); int ret = data->sensor_->setControls(&controls); if (ret) { LOG(VIMC, Error) << "Failed to set controls: " << ret; return ret < 0 ? ret : -EINVAL; } return ret; } int PipelineHandlerVimc::queueRequestDevice(Camera *camera, Request *request) { VimcCameraData *data = cameraData(camera); FrameBuffer *buffer = request->findBuffer(&data->stream_); if (!buffer) { LOG(VIMC, Error) << "Attempt to queue request with invalid stream"; return -ENOENT; } int ret = processControls(data, request); if (ret < 0) return ret; ret = data->video_->queueBuffer(buffer); if (ret < 0) return ret; return 0; } bool PipelineHandlerVimc::match(DeviceEnumerator *enumerator) { DeviceMatch dm("vimc"); dm.add("Raw Capture 0"); dm.add("Raw Capture 1"); dm.add("RGB/YUV Capture"); dm.add("Sensor A"); dm.add("Sensor B"); dm.add("Debayer A"); dm.add("Debayer B"); dm.add("RGB/YUV Input"); dm.add("Scaler"); MediaDevice *media = acquireMediaDevice(enumerator, dm); if (!media) return false; std::unique_ptr data = std::make_unique(this, media); data->ipa_ = IPAManager::instance()->createIPA(this, 0, 0); if (data->ipa_ != nullptr) { std::string conf = data->ipa_->configurationFile("vimc.conf"); data->ipa_->init(IPASettings{ conf }); } else { LOG(VIMC, Warning) << "no matching IPA found"; } /* Locate and open the capture video node. */ if (data->init()) return false; /* Create and register the camera. */ std::string name{ "VIMC " + data->sensor_->model() }; std::set streams{ &data->stream_ }; std::shared_ptr camera = Camera::create(this, name, streams); registerCamera(std::move(camera), std::move(data)); return true; } int VimcCameraData::init() { int ret; ret = media_->disableLinks(); if (ret < 0) return ret; MediaLink *link = media_->link("Debayer B", 1, "Scaler", 0); if (!link) return -ENODEV; ret = link->setEnabled(true); if (ret < 0) return ret; /* Create and open the camera sensor, debayer, scaler and video device. */ sensor_ = new CameraSensor(media_->getEntityByName("Sensor B")); ret = sensor_->init(); if (ret) return ret; debayer_ = new V4L2Subdevice(media_->getEntityByName("Debayer B")); if (debayer_->open()) return -ENODEV; scaler_ = new V4L2Subdevice(media_->getEntityByName("Scaler")); if (scaler_->open()) return -ENODEV; video_ = new V4L2VideoDevice(media_->getEntityByName("RGB/YUV Capture")); if (video_->open()) return -ENODEV; video_->bufferReady.connect(this, &VimcCameraData::bufferReady); raw_ = new V4L2VideoDevice(media_->getEntityByName("Raw Capture 1")); if (raw_->open()) return -ENODEV; /* Initialise the supported controls. */ const ControlInfoMap &controls = sensor_->controls(); ControlInfoMap::Map ctrls; for (const auto &ctrl : controls) { const ControlId *id; ControlInfo info; switch (ctrl.first->id()) { case V4L2_CID_BRIGHTNESS: id = &controls::Brightness; info = ControlInfo{ { -1.0f }, { 1.0f }, { 0.0f } }; break; case V4L2_CID_CONTRAST: id = &controls::Contrast; info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } }; break; case V4L2_CID_SATURATION: id = &controls::Saturation; info = ControlInfo{ { 0.0f }, { 2.0f }, { 1.0f } }; break; default: continue; } ctrls.emplace(id, info); } controlInfo_ = std::move(ctrls); /* Initialize the camera properties. */ properties_ = sensor_->properties(); return 0; } void VimcCameraData::bufferReady(FrameBuffer *buffer) { Request *request = buffer->request(); pipe_->completeBuffer(camera_, request, buffer); pipe_->completeRequest(camera_, request); } REGISTER_PIPELINE_HANDLER(PipelineHandlerVimc); } /* namespace libcamera */