Files
external_libcamera/src/libcamera/pipeline/ipu3/ipu3.cpp
T
Laurent Pinchart 1ec73e9ad5 libcamera: Mark overridden functions with override
Several overridden virtual functions are not marked with override. Fix
it.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
2019-04-26 19:25:11 +03:00

1377 lines
34 KiB
C++

/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* ipu3.cpp - Pipeline handler for Intel IPU3
*/
#include <algorithm>
#include <iomanip>
#include <memory>
#include <vector>
#include <linux/media-bus-format.h>
#include <libcamera/camera.h>
#include <libcamera/request.h>
#include <libcamera/stream.h>
#include "camera_sensor.h"
#include "device_enumerator.h"
#include "log.h"
#include "media_device.h"
#include "pipeline_handler.h"
#include "utils.h"
#include "v4l2_device.h"
#include "v4l2_subdevice.h"
namespace libcamera {
LOG_DEFINE_CATEGORY(IPU3)
class ImgUDevice
{
public:
static constexpr unsigned int PAD_INPUT = 0;
static constexpr unsigned int PAD_OUTPUT = 2;
static constexpr unsigned int PAD_VF = 3;
static constexpr unsigned int PAD_STAT = 4;
/* ImgU output descriptor: group data specific to an ImgU output. */
struct ImgUOutput {
V4L2Device *dev;
unsigned int pad;
std::string name;
BufferPool *pool;
};
ImgUDevice()
: imgu_(nullptr), input_(nullptr)
{
output_.dev = nullptr;
viewfinder_.dev = nullptr;
stat_.dev = nullptr;
}
~ImgUDevice()
{
delete imgu_;
delete input_;
delete output_.dev;
delete viewfinder_.dev;
delete stat_.dev;
}
int init(MediaDevice *media, unsigned int index);
int configureInput(const Size &size,
V4L2DeviceFormat *inputFormat);
int configureOutput(ImgUOutput *output,
const StreamConfiguration &config);
int importBuffers(BufferPool *pool);
int exportBuffers(ImgUOutput *output, BufferPool *pool);
void freeBuffers();
int start();
int stop();
int linkSetup(const std::string &source, unsigned int sourcePad,
const std::string &sink, unsigned int sinkPad,
bool enable);
int enableLinks(bool enable);
unsigned int index_;
std::string name_;
MediaDevice *media_;
V4L2Subdevice *imgu_;
V4L2Device *input_;
ImgUOutput output_;
ImgUOutput viewfinder_;
ImgUOutput stat_;
/* \todo Add param video device for 3A tuning */
BufferPool vfPool_;
BufferPool statPool_;
BufferPool outPool_;
};
class CIO2Device
{
public:
static constexpr unsigned int CIO2_BUFFER_COUNT = 4;
CIO2Device()
: output_(nullptr), csi2_(nullptr), sensor_(nullptr)
{
}
~CIO2Device()
{
delete output_;
delete csi2_;
delete sensor_;
}
int init(const MediaDevice *media, unsigned int index);
int configure(const Size &size,
V4L2DeviceFormat *outputFormat);
BufferPool *exportBuffers();
void freeBuffers();
int start();
int stop();
static int mediaBusToFormat(unsigned int code);
V4L2Device *output_;
V4L2Subdevice *csi2_;
CameraSensor *sensor_;
BufferPool pool_;
};
class IPU3Stream : public Stream
{
public:
IPU3Stream()
: active_(false), device_(nullptr)
{
}
bool active_;
std::string name_;
ImgUDevice::ImgUOutput *device_;
};
class PipelineHandlerIPU3 : public PipelineHandler
{
public:
PipelineHandlerIPU3(CameraManager *manager);
~PipelineHandlerIPU3();
CameraConfiguration
streamConfiguration(Camera *camera,
const std::vector<StreamUsage> &usages) override;
int configureStreams(Camera *camera,
const CameraConfiguration &config) override;
int allocateBuffers(Camera *camera,
const std::set<Stream *> &streams) override;
int freeBuffers(Camera *camera,
const std::set<Stream *> &streams) override;
int start(Camera *camera) override;
void stop(Camera *camera) override;
int queueRequest(Camera *camera, Request *request) override;
bool match(DeviceEnumerator *enumerator) override;
private:
class IPU3CameraData : public CameraData
{
public:
IPU3CameraData(PipelineHandler *pipe)
: CameraData(pipe)
{
}
void imguOutputBufferReady(Buffer *buffer);
void imguInputBufferReady(Buffer *buffer);
void cio2BufferReady(Buffer *buffer);
CIO2Device cio2_;
ImgUDevice *imgu_;
IPU3Stream outStream_;
IPU3Stream vfStream_;
};
static constexpr unsigned int IPU3_BUFFER_COUNT = 4;
IPU3CameraData *cameraData(const Camera *camera)
{
return static_cast<IPU3CameraData *>(
PipelineHandler::cameraData(camera));
}
int registerCameras();
ImgUDevice imgu0_;
ImgUDevice imgu1_;
std::shared_ptr<MediaDevice> cio2MediaDev_;
std::shared_ptr<MediaDevice> imguMediaDev_;
};
PipelineHandlerIPU3::PipelineHandlerIPU3(CameraManager *manager)
: PipelineHandler(manager), cio2MediaDev_(nullptr), imguMediaDev_(nullptr)
{
}
PipelineHandlerIPU3::~PipelineHandlerIPU3()
{
if (cio2MediaDev_)
cio2MediaDev_->release();
if (imguMediaDev_)
imguMediaDev_->release();
}
CameraConfiguration
PipelineHandlerIPU3::streamConfiguration(Camera *camera,
const std::vector<StreamUsage> &usages)
{
IPU3CameraData *data = cameraData(camera);
CameraConfiguration cameraConfig = {};
std::set<IPU3Stream *> streams = {
&data->outStream_,
&data->vfStream_,
};
for (const StreamUsage &usage : usages) {
StreamConfiguration streamConfig = {};
StreamUsage::Role role = usage.role();
IPU3Stream *stream = nullptr;
switch (role) {
case StreamUsage::Role::StillCapture:
/*
* Pick the output stream by default as the Viewfinder
* and VideoRecording roles are not allowed on
* the output stream.
*/
if (streams.find(&data->outStream_) != streams.end()) {
stream = &data->outStream_;
} else if (streams.find(&data->vfStream_) != streams.end()) {
stream = &data->vfStream_;
} else {
LOG(IPU3, Error)
<< "No stream available for requested role "
<< role;
break;
}
/*
* FIXME: Soraka: the maximum resolution reported by
* both sensors (2592x1944 for ov5670 and 4224x3136 for
* ov13858) are returned as default configurations but
* they're not correctly processed by the ImgU.
* Resolutions up tp 2560x1920 have been validated.
*
* \todo Clarify ImgU alignment requirements.
*/
streamConfig.width = 2560;
streamConfig.height = 1920;
break;
case StreamUsage::Role::Viewfinder:
case StreamUsage::Role::VideoRecording: {
/*
* We can't use the 'output' stream for viewfinder or
* video capture usages.
*
* \todo This is an artificial limitation until we
* figure out the exact capabilities of the hardware.
*/
if (streams.find(&data->vfStream_) == streams.end()) {
LOG(IPU3, Error)
<< "No stream available for requested role "
<< role;
break;
}
stream = &data->vfStream_;
/*
* Align the requested viewfinder size to the
* maximum available sensor resolution and to the
* IPU3 alignment constraints.
*/
const Size &res = data->cio2_.sensor_->resolution();
unsigned int width = std::min(usage.size().width,
res.width);
unsigned int height = std::min(usage.size().height,
res.height);
streamConfig.width = width & ~7;
streamConfig.height = height & ~3;
break;
}
default:
LOG(IPU3, Error)
<< "Requested stream role not supported: " << role;
break;
}
if (!stream)
return CameraConfiguration{};
streams.erase(stream);
streamConfig.pixelFormat = V4L2_PIX_FMT_NV12;
streamConfig.bufferCount = IPU3_BUFFER_COUNT;
cameraConfig[stream] = streamConfig;
LOG(IPU3, Debug)
<< "Stream '" << stream->name_ << "' format set to "
<< streamConfig.toString();
}
return cameraConfig;
}
int PipelineHandlerIPU3::configureStreams(Camera *camera,
const CameraConfiguration &config)
{
IPU3CameraData *data = cameraData(camera);
IPU3Stream *outStream = &data->outStream_;
IPU3Stream *vfStream = &data->vfStream_;
CIO2Device *cio2 = &data->cio2_;
ImgUDevice *imgu = data->imgu_;
Size sensorSize = {};
int ret;
outStream->active_ = false;
vfStream->active_ = false;
for (Stream *s : config) {
IPU3Stream *stream = static_cast<IPU3Stream *>(s);
const StreamConfiguration &cfg = config[stream];
/*
* Verify that the requested size respects the IPU3 alignment
* requirements (the image width shall be a multiple of 8
* pixels and its height a multiple of 4 pixels) and the camera
* maximum sizes.
*
* \todo: Consider the BDS scaling factor requirements: "the
* downscaling factor must be an integer value multiple of 1/32"
*/
if (cfg.width % 8 || cfg.height % 4) {
LOG(IPU3, Error)
<< "Invalid stream size: bad alignment";
return -EINVAL;
}
const Size &resolution = cio2->sensor_->resolution();
if (cfg.width > resolution.width ||
cfg.height > resolution.height) {
LOG(IPU3, Error)
<< "Invalid stream size: larger than sensor resolution";
return -EINVAL;
}
/*
* Collect the maximum width and height: IPU3 can downscale
* only.
*/
if (cfg.width > sensorSize.width)
sensorSize.width = cfg.width;
if (cfg.height > sensorSize.height)
sensorSize.height = cfg.height;
stream->active_ = true;
}
/*
* \todo: Enable links selectively based on the requested streams.
* As of now, enable all links unconditionally.
*/
ret = data->imgu_->enableLinks(true);
if (ret)
return ret;
/*
* Pass the requested stream size to the CIO2 unit and get back the
* adjusted format to be propagated to the ImgU output devices.
*/
V4L2DeviceFormat cio2Format = {};
ret = cio2->configure(sensorSize, &cio2Format);
if (ret)
return ret;
ret = imgu->configureInput(sensorSize, &cio2Format);
if (ret)
return ret;
/* Apply the format to the configured streams output devices. */
for (Stream *s : config) {
IPU3Stream *stream = static_cast<IPU3Stream *>(s);
ret = imgu->configureOutput(stream->device_, config[stream]);
if (ret)
return ret;
}
/*
* As we need to set format also on the non-active streams, use
* the configuration of the active one for that purpose (there should
* be at least one active stream in the configuration request).
*/
if (!outStream->active_) {
ret = imgu->configureOutput(outStream->device_,
config[vfStream]);
if (ret)
return ret;
}
if (!vfStream->active_) {
ret = imgu->configureOutput(vfStream->device_,
config[outStream]);
if (ret)
return ret;
}
/*
* Apply the largest available format to the stat node.
* \todo Revise this when we'll actually use the stat node.
*/
StreamConfiguration statConfig = {};
statConfig.width = cio2Format.width;
statConfig.height = cio2Format.height;
ret = imgu->configureOutput(&imgu->stat_, statConfig);
if (ret)
return ret;
return 0;
}
/**
* \todo Clarify if 'viewfinder' and 'stat' nodes have to be set up and
* started even if not in use. As of now, if not properly configured and
* enabled, the ImgU processing pipeline stalls.
*
* In order to be able to start the 'viewfinder' and 'stat' nodes, we need
* memory to be reserved.
*/
int PipelineHandlerIPU3::allocateBuffers(Camera *camera,
const std::set<Stream *> &streams)
{
IPU3CameraData *data = cameraData(camera);
IPU3Stream *outStream = &data->outStream_;
IPU3Stream *vfStream = &data->vfStream_;
CIO2Device *cio2 = &data->cio2_;
ImgUDevice *imgu = data->imgu_;
unsigned int bufferCount;
int ret;
/* Share buffers between CIO2 output and ImgU input. */
BufferPool *pool = cio2->exportBuffers();
if (!pool)
return -ENOMEM;
ret = imgu->importBuffers(pool);
if (ret)
goto error;
/*
* Use for the stat's internal pool the same number of buffer as
* for the input pool.
* \todo To be revised when we'll actually use the stat node.
*/
bufferCount = pool->count();
imgu->stat_.pool->createBuffers(bufferCount);
ret = imgu->exportBuffers(&imgu->stat_, imgu->stat_.pool);
if (ret)
goto error;
/* Allocate buffers for each active stream. */
for (Stream *s : streams) {
IPU3Stream *stream = static_cast<IPU3Stream *>(s);
ImgUDevice::ImgUOutput *dev = stream->device_;
ret = imgu->exportBuffers(dev, &stream->bufferPool());
if (ret)
goto error;
}
/*
* Allocate buffers also on non-active outputs; use the same number
* of buffers as the active ones.
*/
if (!outStream->active_) {
bufferCount = vfStream->bufferPool().count();
outStream->device_->pool->createBuffers(bufferCount);
ret = imgu->exportBuffers(outStream->device_,
outStream->device_->pool);
if (ret)
goto error;
}
if (!vfStream->active_) {
bufferCount = outStream->bufferPool().count();
vfStream->device_->pool->createBuffers(bufferCount);
ret = imgu->exportBuffers(vfStream->device_,
vfStream->device_->pool);
if (ret)
goto error;
}
return 0;
error:
freeBuffers(camera, streams);
return ret;
}
int PipelineHandlerIPU3::freeBuffers(Camera *camera,
const std::set<Stream *> &streams)
{
IPU3CameraData *data = cameraData(camera);
data->cio2_.freeBuffers();
data->imgu_->freeBuffers();
return 0;
}
int PipelineHandlerIPU3::start(Camera *camera)
{
IPU3CameraData *data = cameraData(camera);
CIO2Device *cio2 = &data->cio2_;
ImgUDevice *imgu = data->imgu_;
int ret;
/*
* Start the ImgU video devices, buffers will be queued to the
* ImgU output and viewfinder when requests will be queued.
*/
ret = cio2->start();
if (ret)
goto error;
ret = imgu->start();
if (ret) {
imgu->stop();
cio2->stop();
goto error;
}
return 0;
error:
LOG(IPU3, Error) << "Failed to start camera " << camera->name();
return ret;
}
void PipelineHandlerIPU3::stop(Camera *camera)
{
IPU3CameraData *data = cameraData(camera);
int ret;
ret = data->cio2_.stop();
ret |= data->imgu_->stop();
if (ret)
LOG(IPU3, Warning) << "Failed to stop camera "
<< camera->name();
PipelineHandler::stop(camera);
}
int PipelineHandlerIPU3::queueRequest(Camera *camera, Request *request)
{
int error = 0;
for (auto it : request->buffers()) {
IPU3Stream *stream = static_cast<IPU3Stream *>(it.first);
Buffer *buffer = it.second;
int ret = stream->device_->dev->queueBuffer(buffer);
if (ret < 0)
error = ret;
}
PipelineHandler::queueRequest(camera, request);
return error;
}
bool PipelineHandlerIPU3::match(DeviceEnumerator *enumerator)
{
int ret;
DeviceMatch cio2_dm("ipu3-cio2");
cio2_dm.add("ipu3-csi2 0");
cio2_dm.add("ipu3-cio2 0");
cio2_dm.add("ipu3-csi2 1");
cio2_dm.add("ipu3-cio2 1");
cio2_dm.add("ipu3-csi2 2");
cio2_dm.add("ipu3-cio2 2");
cio2_dm.add("ipu3-csi2 3");
cio2_dm.add("ipu3-cio2 3");
DeviceMatch imgu_dm("ipu3-imgu");
imgu_dm.add("ipu3-imgu 0");
imgu_dm.add("ipu3-imgu 0 input");
imgu_dm.add("ipu3-imgu 0 parameters");
imgu_dm.add("ipu3-imgu 0 output");
imgu_dm.add("ipu3-imgu 0 viewfinder");
imgu_dm.add("ipu3-imgu 0 3a stat");
imgu_dm.add("ipu3-imgu 1");
imgu_dm.add("ipu3-imgu 1 input");
imgu_dm.add("ipu3-imgu 1 parameters");
imgu_dm.add("ipu3-imgu 1 output");
imgu_dm.add("ipu3-imgu 1 viewfinder");
imgu_dm.add("ipu3-imgu 1 3a stat");
/*
* It is safe to acquire both media devices at this point as
* DeviceEnumerator::search() skips the busy ones for us.
*/
cio2MediaDev_ = enumerator->search(cio2_dm);
if (!cio2MediaDev_)
return false;
cio2MediaDev_->acquire();
imguMediaDev_ = enumerator->search(imgu_dm);
if (!imguMediaDev_)
return false;
imguMediaDev_->acquire();
/*
* Disable all links that are enabled by default on CIO2, as camera
* creation enables all valid links it finds.
*
* Close the CIO2 media device after, as links are enabled and should
* not need to be changed after.
*/
if (cio2MediaDev_->open())
return false;
if (cio2MediaDev_->disableLinks()) {
cio2MediaDev_->close();
return false;
}
if (imguMediaDev_->open()) {
cio2MediaDev_->close();
return false;
}
/*
* FIXME: enabled links in one ImgU instance interfere with capture
* operations on the other one. This can be easily triggered by
* capturing from one camera and then trying to capture from the other
* one right after, without disabling media links in the media graph
* first.
*
* The tricky part here is where to disable links on the ImgU instance
* which is currently not in use:
* 1) Link enable/disable cannot be done at start/stop time as video
* devices needs to be linked first before format can be configured on
* them.
* 2) As link enable has to be done at the least in configureStreams,
* before configuring formats, the only place where to disable links
* would be 'stop()', but the Camera class state machine allows
* start()<->stop() sequences without any streamConfiguration() in
* between.
*
* As of now, disable all links in the media graph at 'match()' time,
* to allow testing different cameras in different test applications
* runs. A test application that would use two distinct cameras without
* going through a library teardown->match() sequence would fail
* at the moment.
*/
ret = imguMediaDev_->disableLinks();
if (ret)
goto error;
ret = registerCameras();
error:
cio2MediaDev_->close();
imguMediaDev_->close();
return ret == 0;
}
/**
* \brief Initialise ImgU and CIO2 devices associated with cameras
*
* Initialise the two ImgU instances and create cameras with an associated
* CIO2 device instance.
*
* \return 0 on success or a negative error code for error or if no camera
* has been created
* \retval -ENODEV no camera has been created
*/
int PipelineHandlerIPU3::registerCameras()
{
int ret;
ret = imgu0_.init(imguMediaDev_.get(), 0);
if (ret)
return ret;
ret = imgu1_.init(imguMediaDev_.get(), 1);
if (ret)
return ret;
/*
* For each CSI-2 receiver on the IPU3, create a Camera if an
* image sensor is connected to it and the sensor can produce images
* in a compatible format.
*/
unsigned int numCameras = 0;
for (unsigned int id = 0; id < 4 && numCameras < 2; ++id) {
std::unique_ptr<IPU3CameraData> data =
utils::make_unique<IPU3CameraData>(this);
std::set<Stream *> streams = {
&data->outStream_,
&data->vfStream_,
};
CIO2Device *cio2 = &data->cio2_;
ret = cio2->init(cio2MediaDev_.get(), id);
if (ret)
continue;
/**
* \todo Dynamically assign ImgU and output devices to each
* stream and camera; as of now, limit support to two cameras
* only, and assign imgu0 to the first one and imgu1 to the
* second.
*/
data->imgu_ = numCameras ? &imgu1_ : &imgu0_;
data->outStream_.device_ = &data->imgu_->output_;
data->outStream_.name_ = "output";
data->vfStream_.device_ = &data->imgu_->viewfinder_;
data->vfStream_.name_ = "viewfinder";
/*
* Connect video devices' 'bufferReady' signals to their
* slot to implement the image processing pipeline.
*
* Frames produced by the CIO2 unit are passed to the
* associated ImgU input where they get processed and
* returned through the ImgU main and secondary outputs.
*/
data->cio2_.output_->bufferReady.connect(data.get(),
&IPU3CameraData::cio2BufferReady);
data->imgu_->input_->bufferReady.connect(data.get(),
&IPU3CameraData::imguInputBufferReady);
data->imgu_->output_.dev->bufferReady.connect(data.get(),
&IPU3CameraData::imguOutputBufferReady);
data->imgu_->viewfinder_.dev->bufferReady.connect(data.get(),
&IPU3CameraData::imguOutputBufferReady);
/* Create and register the Camera instance. */
std::string cameraName = cio2->sensor_->entity()->name() + " "
+ std::to_string(id);
std::shared_ptr<Camera> camera = Camera::create(this,
cameraName,
streams);
registerCamera(std::move(camera), std::move(data));
LOG(IPU3, Info)
<< "Registered Camera[" << numCameras << "] \""
<< cameraName << "\""
<< " connected to CSI-2 receiver " << id;
numCameras++;
}
return numCameras ? 0 : -ENODEV;
}
/* -----------------------------------------------------------------------------
* Buffer Ready slots
*/
/**
* \brief Handle buffers completion at the ImgU input
* \param[in] buffer The completed buffer
*
* Buffers completed from the ImgU input are immediately queued back to the
* CIO2 unit to continue frame capture.
*/
void PipelineHandlerIPU3::IPU3CameraData::imguInputBufferReady(Buffer *buffer)
{
cio2_.output_->queueBuffer(buffer);
}
/**
* \brief Handle buffers completion at the ImgU output
* \param[in] buffer The completed buffer
*
* Buffers completed from the ImgU output are directed to the application.
*/
void PipelineHandlerIPU3::IPU3CameraData::imguOutputBufferReady(Buffer *buffer)
{
Request *request = buffer->request();
if (!pipe_->completeBuffer(camera_, request, buffer))
/* Request not completed yet, return here. */
return;
/* Complete the pending requests in queuing order. */
while (1) {
request = queuedRequests_.front();
if (request->hasPendingBuffers())
break;
pipe_->completeRequest(camera_, request);
}
}
/**
* \brief Handle buffers completion at the CIO2 output
* \param[in] buffer The completed buffer
*
* Buffers completed from the CIO2 are immediately queued to the ImgU unit
* for further processing.
*/
void PipelineHandlerIPU3::IPU3CameraData::cio2BufferReady(Buffer *buffer)
{
imgu_->input_->queueBuffer(buffer);
}
/* -----------------------------------------------------------------------------
* ImgU Device
*/
/**
* \brief Initialize components of the ImgU instance
* \param[in] mediaDevice The ImgU instance media device
* \param[in] index The ImgU instance index
*
* Create and open the V4L2 devices and subdevices of the ImgU instance
* with \a index.
*
* In case of errors the created V4L2Device and V4L2Subdevice instances
* are destroyed at pipeline handler delete time.
*
* \return 0 on success or a negative error code otherwise
*/
int ImgUDevice::init(MediaDevice *media, unsigned int index)
{
int ret;
index_ = index;
name_ = "ipu3-imgu " + std::to_string(index_);
media_ = media;
/*
* The media entities presence in the media device has been verified
* by the match() function: no need to check for newly created
* video devices and subdevice validity here.
*/
imgu_ = V4L2Subdevice::fromEntityName(media, name_);
ret = imgu_->open();
if (ret)
return ret;
input_ = V4L2Device::fromEntityName(media, name_ + " input");
ret = input_->open();
if (ret)
return ret;
output_.dev = V4L2Device::fromEntityName(media, name_ + " output");
ret = output_.dev->open();
if (ret)
return ret;
output_.pad = PAD_OUTPUT;
output_.name = "output";
output_.pool = &outPool_;
viewfinder_.dev = V4L2Device::fromEntityName(media,
name_ + " viewfinder");
ret = viewfinder_.dev->open();
if (ret)
return ret;
viewfinder_.pad = PAD_VF;
viewfinder_.name = "viewfinder";
viewfinder_.pool = &vfPool_;
stat_.dev = V4L2Device::fromEntityName(media, name_ + " 3a stat");
ret = stat_.dev->open();
if (ret)
return ret;
stat_.pad = PAD_STAT;
stat_.name = "stat";
stat_.pool = &statPool_;
return 0;
}
/**
* \brief Configure the ImgU unit input
* \param[in] size The ImgU input frame size
* \param[in] inputFormat The format to be applied to ImgU input
* \return 0 on success or a negative error code otherwise
*/
int ImgUDevice::configureInput(const Size &size,
V4L2DeviceFormat *inputFormat)
{
/* Configure the ImgU input video device with the requested sizes. */
int ret = input_->setFormat(inputFormat);
if (ret)
return ret;
LOG(IPU3, Debug) << "ImgU input format = " << inputFormat->toString();
/*
* \todo The IPU3 driver implementation shall be changed to use the
* input sizes as 'ImgU Input' subdevice sizes, and use the desired
* GDC output sizes to configure the crop/compose rectangles.
*
* The current IPU3 driver implementation uses GDC sizes as the
* 'ImgU Input' subdevice sizes, and the input video device sizes
* to configure the crop/compose rectangles, contradicting the
* V4L2 specification.
*/
Rectangle rect = {
.x = 0,
.y = 0,
.w = inputFormat->width,
.h = inputFormat->height,
};
ret = imgu_->setCrop(PAD_INPUT, &rect);
if (ret)
return ret;
ret = imgu_->setCompose(PAD_INPUT, &rect);
if (ret)
return ret;
LOG(IPU3, Debug) << "ImgU input feeder and BDS rectangle = "
<< rect.toString();
V4L2SubdeviceFormat imguFormat = {};
imguFormat.width = size.width;
imguFormat.height = size.height;
imguFormat.mbus_code = MEDIA_BUS_FMT_FIXED;
ret = imgu_->setFormat(PAD_INPUT, &imguFormat);
if (ret)
return ret;
LOG(IPU3, Debug) << "ImgU GDC format = " << imguFormat.toString();
return 0;
}
/**
* \brief Configure the ImgU unit \a id video output
* \param[in] output The ImgU output device to configure
* \param[in] config The requested configuration
* \return 0 on success or a negative error code otherwise
*/
int ImgUDevice::configureOutput(ImgUOutput *output,
const StreamConfiguration &config)
{
V4L2Device *dev = output->dev;
unsigned int pad = output->pad;
V4L2SubdeviceFormat imguFormat = {};
imguFormat.width = config.width;
imguFormat.height = config.height;
imguFormat.mbus_code = MEDIA_BUS_FMT_FIXED;
int ret = imgu_->setFormat(pad, &imguFormat);
if (ret)
return ret;
/* No need to apply format to the stat node. */
if (output == &stat_)
return 0;
V4L2DeviceFormat outputFormat = {};
outputFormat.width = config.width;
outputFormat.height = config.height;
outputFormat.fourcc = V4L2_PIX_FMT_NV12;
outputFormat.planesCount = 2;
ret = dev->setFormat(&outputFormat);
if (ret)
return ret;
LOG(IPU3, Debug) << "ImgU " << output->name << " format = "
<< outputFormat.toString();
return 0;
}
/**
* \brief Import buffers from \a pool into the ImgU input
* \param[in] pool The buffer pool to import
* \return 0 on success or a negative error code otherwise
*/
int ImgUDevice::importBuffers(BufferPool *pool)
{
int ret = input_->importBuffers(pool);
if (ret) {
LOG(IPU3, Error) << "Failed to import ImgU input buffers";
return ret;
}
return 0;
}
/**
* \brief Export buffers from \a output to the provided \a pool
* \param[in] output The ImgU output device
* \param[in] pool The buffer pool where to export buffers
*
* Export memory buffers reserved in the video device memory associated with
* \a output id to the buffer pool provided as argument.
*
* \return 0 on success or a negative error code otherwise
*/
int ImgUDevice::exportBuffers(ImgUOutput *output, BufferPool *pool)
{
int ret = output->dev->exportBuffers(pool);
if (ret) {
LOG(IPU3, Error) << "Failed to export ImgU "
<< output->name << " buffers";
return ret;
}
return 0;
}
/**
* \brief Release buffers for all the ImgU video devices
*/
void ImgUDevice::freeBuffers()
{
int ret;
ret = output_.dev->releaseBuffers();
if (ret)
LOG(IPU3, Error) << "Failed to release ImgU output buffers";
ret = stat_.dev->releaseBuffers();
if (ret)
LOG(IPU3, Error) << "Failed to release ImgU stat buffers";
ret = viewfinder_.dev->releaseBuffers();
if (ret)
LOG(IPU3, Error) << "Failed to release ImgU viewfinder buffers";
ret = input_->releaseBuffers();
if (ret)
LOG(IPU3, Error) << "Failed to release ImgU input buffers";
}
int ImgUDevice::start()
{
int ret;
/* Start the ImgU video devices. */
ret = output_.dev->streamOn();
if (ret) {
LOG(IPU3, Error) << "Failed to start ImgU output";
return ret;
}
ret = viewfinder_.dev->streamOn();
if (ret) {
LOG(IPU3, Error) << "Failed to start ImgU viewfinder";
return ret;
}
ret = stat_.dev->streamOn();
if (ret) {
LOG(IPU3, Error) << "Failed to start ImgU stat";
return ret;
}
ret = input_->streamOn();
if (ret) {
LOG(IPU3, Error) << "Failed to start ImgU input";
return ret;
}
return 0;
}
int ImgUDevice::stop()
{
int ret;
ret = output_.dev->streamOff();
ret |= viewfinder_.dev->streamOff();
ret |= stat_.dev->streamOff();
ret |= input_->streamOff();
return ret;
}
/**
* \brief Enable or disable a single link on the ImgU instance
*
* This method assumes the media device associated with the ImgU instance
* is open.
*
* \return 0 on success or a negative error code otherwise
*/
int ImgUDevice::linkSetup(const std::string &source, unsigned int sourcePad,
const std::string &sink, unsigned int sinkPad,
bool enable)
{
MediaLink *link = media_->link(source, sourcePad, sink, sinkPad);
if (!link) {
LOG(IPU3, Error)
<< "Failed to get link: '" << source << "':"
<< sourcePad << " -> '" << sink << "':" << sinkPad;
return -ENODEV;
}
return link->setEnabled(enable);
}
/**
* \brief Enable or disable all media links in the ImgU instance to prepare
* for capture operations
*
* \todo This method will probably be removed or changed once links will be
* enabled or disabled selectively.
*
* \return 0 on success or a negative error code otherwise
*/
int ImgUDevice::enableLinks(bool enable)
{
std::string viewfinderName = name_ + " viewfinder";
std::string outputName = name_ + " output";
std::string statName = name_ + " 3a stat";
std::string inputName = name_ + " input";
int ret;
/* \todo Establish rules to handle media devices open/close. */
ret = media_->open();
if (ret)
return ret;
ret = linkSetup(inputName, 0, name_, PAD_INPUT, enable);
if (ret)
goto done;
ret = linkSetup(name_, PAD_OUTPUT, outputName, 0, enable);
if (ret)
goto done;
ret = linkSetup(name_, PAD_VF, viewfinderName, 0, enable);
if (ret)
goto done;
ret = linkSetup(name_, PAD_STAT, statName, 0, enable);
done:
media_->close();
return ret;
}
/*------------------------------------------------------------------------------
* CIO2 Device
*/
/**
* \brief Initialize components of the CIO2 device with \a index
* \param[in] media The CIO2 media device
* \param[in] index The CIO2 device index
*
* Create and open the video device and subdevices in the CIO2 instance at \a
* index, if a supported image sensor is connected to the CSI-2 receiver of
* this CIO2 instance. Enable the media links connecting the CIO2 components
* to prepare for capture operations and cached the sensor maximum size.
*
* \return 0 on success or a negative error code otherwise
* \retval -ENODEV No supported image sensor is connected to this CIO2 instance
*/
int CIO2Device::init(const MediaDevice *media, unsigned int index)
{
int ret;
/*
* Verify that a sensor subdevice is connected to this CIO2 instance
* and enable the media link between the two.
*/
std::string csi2Name = "ipu3-csi2 " + std::to_string(index);
MediaEntity *csi2Entity = media->getEntityByName(csi2Name);
const std::vector<MediaPad *> &pads = csi2Entity->pads();
if (pads.empty())
return -ENODEV;
/* IPU3 CSI-2 receivers have a single sink pad at index 0. */
MediaPad *sink = pads[0];
const std::vector<MediaLink *> &links = sink->links();
if (links.empty())
return -ENODEV;
MediaLink *link = links[0];
MediaEntity *sensorEntity = link->source()->entity();
sensor_ = new CameraSensor(sensorEntity);
ret = sensor_->init();
if (ret)
return ret;
ret = link->setEnabled(true);
if (ret)
return ret;
/*
* Make sure the sensor produces at least one format compatible with
* the CIO2 requirements.
*
* utils::set_overlap requires the ranges to be sorted, keep the
* cio2Codes vector sorted in ascending order.
*/
const std::vector<unsigned int> cio2Codes{ MEDIA_BUS_FMT_SBGGR10_1X10,
MEDIA_BUS_FMT_SGRBG10_1X10,
MEDIA_BUS_FMT_SGBRG10_1X10,
MEDIA_BUS_FMT_SRGGB10_1X10 };
const std::vector<unsigned int> &sensorCodes = sensor_->mbusCodes();
if (!utils::set_overlap(sensorCodes.begin(), sensorCodes.end(),
cio2Codes.begin(), cio2Codes.end())) {
LOG(IPU3, Error)
<< "Sensor " << sensor_->entity()->name()
<< " has not format compatible with the IPU3";
return -EINVAL;
}
/*
* \todo Define when to open and close video device nodes, as they
* might impact on power consumption.
*/
csi2_ = new V4L2Subdevice(csi2Entity);
ret = csi2_->open();
if (ret)
return ret;
std::string cio2Name = "ipu3-cio2 " + std::to_string(index);
output_ = V4L2Device::fromEntityName(media, cio2Name);
ret = output_->open();
if (ret)
return ret;
return 0;
}
/**
* \brief Configure the CIO2 unit
* \param[in] size The requested CIO2 output frame size
* \param[out] outputFormat The CIO2 unit output image format
* \return 0 on success or a negative error code otherwise
*/
int CIO2Device::configure(const Size &size,
V4L2DeviceFormat *outputFormat)
{
V4L2SubdeviceFormat sensorFormat;
int ret;
/*
* Apply the selected format to the sensor, the CSI-2 receiver and
* the CIO2 output device.
*/
sensorFormat = sensor_->getFormat({ MEDIA_BUS_FMT_SBGGR10_1X10,
MEDIA_BUS_FMT_SGBRG10_1X10,
MEDIA_BUS_FMT_SGRBG10_1X10,
MEDIA_BUS_FMT_SRGGB10_1X10 },
size);
ret = sensor_->setFormat(&sensorFormat);
if (ret)
return ret;
ret = csi2_->setFormat(0, &sensorFormat);
if (ret)
return ret;
outputFormat->width = sensorFormat.width;
outputFormat->height = sensorFormat.height;
outputFormat->fourcc = mediaBusToFormat(sensorFormat.mbus_code);
outputFormat->planesCount = 1;
ret = output_->setFormat(outputFormat);
if (ret)
return ret;
LOG(IPU3, Debug) << "CIO2 output format " << outputFormat->toString();
return 0;
}
/**
* \brief Allocate CIO2 memory buffers and export them in a BufferPool
*
* Allocate memory buffers in the CIO2 video device and export them to
* a buffer pool that can be imported by another device.
*
* \return The buffer pool with export buffers on success or nullptr otherwise
*/
BufferPool *CIO2Device::exportBuffers()
{
pool_.createBuffers(CIO2_BUFFER_COUNT);
int ret = output_->exportBuffers(&pool_);
if (ret) {
LOG(IPU3, Error) << "Failed to export CIO2 buffers";
return nullptr;
}
return &pool_;
}
void CIO2Device::freeBuffers()
{
if (output_->releaseBuffers())
LOG(IPU3, Error) << "Failed to release CIO2 buffers";
}
int CIO2Device::start()
{
int ret;
for (Buffer &buffer : pool_.buffers()) {
ret = output_->queueBuffer(&buffer);
if (ret)
return ret;
}
ret = output_->streamOn();
if (ret)
return ret;
return 0;
}
int CIO2Device::stop()
{
return output_->streamOff();
}
int CIO2Device::mediaBusToFormat(unsigned int code)
{
switch (code) {
case MEDIA_BUS_FMT_SBGGR10_1X10:
return V4L2_PIX_FMT_IPU3_SBGGR10;
case MEDIA_BUS_FMT_SGBRG10_1X10:
return V4L2_PIX_FMT_IPU3_SGBRG10;
case MEDIA_BUS_FMT_SGRBG10_1X10:
return V4L2_PIX_FMT_IPU3_SGRBG10;
case MEDIA_BUS_FMT_SRGGB10_1X10:
return V4L2_PIX_FMT_IPU3_SRGGB10;
default:
return -EINVAL;
}
}
REGISTER_PIPELINE_HANDLER(PipelineHandlerIPU3);
} /* namespace libcamera */