[{"id":1455,"web_url":"https://patchwork.libcamera.org/comment/1455/","msgid":"<20190418145541.liz5liaxaijzai43@uno.localdomain>","date":"2019-04-18T14:55:41","subject":"Re: [libcamera-devel] [PATCH v3 13/13] libcamera: pipeline: Add\n\tRKISP1 pipeline","submitter":{"id":3,"url":"https://patchwork.libcamera.org/api/people/3/","name":"Jacopo Mondi","email":"jacopo@jmondi.org"},"content":"Hi Laurent,\n\nOn Thu, Apr 18, 2019 at 05:14:37PM +0300, Laurent Pinchart wrote:\n> The pipeline handler for the Rockchip ISP creates one camera instance\n> per detected raw Bayer CSI-2 sensor. Parallel sensors and YUV sensors\n> are not supported yet.\n>\n> As the ISP has a single CSI-2 receiver, only one camera can be used at a\n> time. Mutual exclusion isn't implemented yet.\n>\n> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n> Tested-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n> ---\n> Changes since v2:\n>\n> - Print the format configured at the sensor output\n> - Close the media device when match() fails\n> - Use V4L2Subdevice::fromEntityName() and V4L2Device::fromEntityName()\n> - Rebased on top of the multi-stream preparation commits\n>\n> Changes since v1:\n>\n> - Move sensor entity sanity checks to CameraSensor class\n> - Remove unneeded LOG() message\n> ---\n>  src/libcamera/pipeline/meson.build        |   1 +\n>  src/libcamera/pipeline/rkisp1/meson.build |   3 +\n>  src/libcamera/pipeline/rkisp1/rkisp1.cpp  | 448 ++++++++++++++++++++++\n>  3 files changed, 452 insertions(+)\n>  create mode 100644 src/libcamera/pipeline/rkisp1/meson.build\n>  create mode 100644 src/libcamera/pipeline/rkisp1/rkisp1.cpp\n>\n> diff --git a/src/libcamera/pipeline/meson.build b/src/libcamera/pipeline/meson.build\n> index 40bb26405b88..0d466225a72e 100644\n> --- a/src/libcamera/pipeline/meson.build\n> +++ b/src/libcamera/pipeline/meson.build\n> @@ -4,3 +4,4 @@ libcamera_sources += files([\n>  ])\n>\n>  subdir('ipu3')\n> +subdir('rkisp1')\n> diff --git a/src/libcamera/pipeline/rkisp1/meson.build b/src/libcamera/pipeline/rkisp1/meson.build\n> new file mode 100644\n> index 000000000000..f1cc4046b5d0\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/rkisp1/meson.build\n> @@ -0,0 +1,3 @@\n> +libcamera_sources += files([\n> +    'rkisp1.cpp',\n> +])\n> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> new file mode 100644\n> index 000000000000..71faa880db49\n> --- /dev/null\n> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> @@ -0,0 +1,448 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2019, Google Inc.\n> + *\n> + * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n> + */\n> +\n> +#include <iomanip>\n> +#include <memory>\n> +#include <vector>\n> +\n> +#include <linux/media-bus-format.h>\n> +\n> +#include <libcamera/camera.h>\n> +#include <libcamera/request.h>\n> +#include <libcamera/stream.h>\n> +\n> +#include \"camera_sensor.h\"\n> +#include \"device_enumerator.h\"\n> +#include \"log.h\"\n> +#include \"media_device.h\"\n> +#include \"pipeline_handler.h\"\n> +#include \"utils.h\"\n> +#include \"v4l2_device.h\"\n> +#include \"v4l2_subdevice.h\"\n> +\n> +namespace libcamera {\n> +\n> +LOG_DEFINE_CATEGORY(RkISP1)\n> +\n> +class PipelineHandlerRkISP1 : public PipelineHandler\n> +{\n> +public:\n> +\tPipelineHandlerRkISP1(CameraManager *manager);\n> +\t~PipelineHandlerRkISP1();\n> +\n> +\tCameraConfiguration streamConfiguration(Camera *camera,\n> +\t\tconst std::vector<StreamUsage> &usages) override;\n> +\tint configureStreams(Camera *camera,\n> +\t\tconst CameraConfiguration &config) override;\n> +\n> +\tint allocateBuffers(Camera *camera,\n> +\t\tconst std::set<Stream *> &streams) override;\n> +\tint freeBuffers(Camera *camera,\n> +\t\tconst std::set<Stream *> &streams) override;\n> +\n> +\tint start(Camera *camera) override;\n> +\tvoid stop(Camera *camera) override;\n> +\n> +\tint queueRequest(Camera *camera, Request *request) override;\n> +\n> +\tbool match(DeviceEnumerator *enumerator) override;\n> +\n> +private:\n> +\tclass RkISP1CameraData : public CameraData\n> +\t{\n> +\tpublic:\n> +\t\tRkISP1CameraData(PipelineHandler *pipe)\n> +\t\t\t: CameraData(pipe), sensor_(nullptr)\n> +\t\t{\n> +\t\t}\n> +\n> +\t\t~RkISP1CameraData()\n> +\t\t{\n> +\t\t\tdelete sensor_;\n> +\t\t}\n> +\n> +\t\tStream stream_;\n> +\t\tCameraSensor *sensor_;\n> +\t};\n> +\n> +\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> +\n> +\tRkISP1CameraData *cameraData(const Camera *camera)\n> +\t{\n> +\t\treturn static_cast<RkISP1CameraData *>(\n> +\t\t\tPipelineHandler::cameraData(camera));\n> +\t}\n> +\n> +\tint initLinks();\n> +\tint createCamera(MediaEntity *sensor);\n> +\tvoid bufferReady(Buffer *buffer);\n> +\n> +\tstd::shared_ptr<MediaDevice> media_;\n> +\tV4L2Subdevice *dphy_;\n> +\tV4L2Subdevice *isp_;\n> +\tV4L2Device *video_;\n> +\n> +\tCamera *activeCamera_;\n> +};\n> +\n> +PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)\n> +\t: PipelineHandler(manager), dphy_(nullptr), isp_(nullptr),\n> +\t  video_(nullptr)\n> +{\n> +}\n> +\n> +PipelineHandlerRkISP1::~PipelineHandlerRkISP1()\n> +{\n> +\tdelete video_;\n> +\tdelete isp_;\n> +\tdelete dphy_;\n> +\n> +\tif (media_)\n> +\t\tmedia_->release();\n> +}\n> +\n> +/* -----------------------------------------------------------------------------\n> + * Pipeline Operations\n> + */\n> +\n> +CameraConfiguration PipelineHandlerRkISP1::streamConfiguration(Camera *camera,\n> +\tconst std::vector<StreamUsage> &usages)\n> +{\n> +\tRkISP1CameraData *data = cameraData(camera);\n> +\tCameraConfiguration configs;\n> +\tStreamConfiguration config{};\n> +\n> +\tconst Size &resolution = data->sensor_->resolution();\n> +\tconfig.width = resolution.width;\n> +\tconfig.height = resolution.height;\n> +\tconfig.pixelFormat = V4L2_PIX_FMT_NV12;\n> +\tconfig.bufferCount = RKISP1_BUFFER_COUNT;\n> +\n> +\tconfigs[&data->stream_] = config;\n> +\n> +\tLOG(RkISP1, Debug)\n> +\t\t<< \"Stream format set to \" << config.width << \"x\"\n> +\t\t<< config.height << \"-0x\" << std::hex << std::setfill('0')\n> +\t\t<< std::setw(8) << config.pixelFormat;\n> +\n> +\treturn configs;\n> +}\n> +\n> +int PipelineHandlerRkISP1::configureStreams(Camera *camera,\n> +\t\t\t\t\t    const CameraConfiguration &config)\n> +{\n> +\tRkISP1CameraData *data = cameraData(camera);\n> +\tconst StreamConfiguration &cfg = config[&data->stream_];\n> +\tCameraSensor *sensor = data->sensor_;\n> +\tint ret;\n> +\n> +\t/* Verify the configuration. */\n> +\tconst Size &resolution = sensor->resolution();\n> +\tif (cfg.width > resolution.width ||\n> +\t    cfg.height > resolution.height) {\n> +\t\tLOG(RkISP1, Error)\n> +\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\t/*\n> +\t * Configure the sensor links: enable the link corresponding to this\n> +\t * camera and disable all the other sensor links.\n> +\t */\n> +\tconst MediaPad *pad = dphy_->entity()->getPadByIndex(0);\n> +\n> +\tret = media_->open();\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tfor (MediaLink *link : pad->links()) {\n> +\t\tbool enable = link->source()->entity() == sensor->entity();\n> +\n> +\t\tif (!!(link->flags() & MEDIA_LNK_FL_ENABLED) == enable)\n> +\t\t\tcontinue;\n> +\n> +\t\tLOG(RkISP1, Debug)\n> +\t\t\t<< (enable ? \"Enabling\" : \"Disabling\")\n> +\t\t\t<< \" link from sensor '\"\n> +\t\t\t<< link->source()->entity()->name()\n> +\t\t\t<< \"' to CSI-2 receiver\";\n> +\n> +\t\tret = link->setEnabled(enable);\n> +\t\tif (ret < 0)\n> +\t\t\tbreak;\n> +\t}\n> +\n> +\tmedia_->close();\n> +\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\t/*\n> +\t * Configure the format on the sensor output and propagate it through\n> +\t * the pipeline.\n> +\t */\n> +\tV4L2SubdeviceFormat format;\n> +\tformat = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> +\t\t\t\t     MEDIA_BUS_FMT_SGBRG12_1X12,\n> +\t\t\t\t     MEDIA_BUS_FMT_SGRBG12_1X12,\n> +\t\t\t\t     MEDIA_BUS_FMT_SRGGB12_1X12,\n> +\t\t\t\t     MEDIA_BUS_FMT_SBGGR10_1X10,\n> +\t\t\t\t     MEDIA_BUS_FMT_SGBRG10_1X10,\n> +\t\t\t\t     MEDIA_BUS_FMT_SGRBG10_1X10,\n> +\t\t\t\t     MEDIA_BUS_FMT_SRGGB10_1X10,\n> +\t\t\t\t     MEDIA_BUS_FMT_SBGGR8_1X8,\n> +\t\t\t\t     MEDIA_BUS_FMT_SGBRG8_1X8,\n> +\t\t\t\t     MEDIA_BUS_FMT_SGRBG8_1X8,\n> +\t\t\t\t     MEDIA_BUS_FMT_SRGGB8_1X8 },\n> +\t\t\t\t   Size(cfg.width, cfg.height));\n> +\n> +\tLOG(RkISP1, Debug) << \"Configuring sensor with \" << format.toString();\n> +\n> +\tret = sensor->setFormat(&format);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tLOG(RkISP1, Debug) << \"Sensor configured with \" << format.toString();\n> +\n> +\tret = dphy_->setFormat(0, &format);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tret = dphy_->getFormat(1, &format);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tret = isp_->setFormat(0, &format);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tV4L2DeviceFormat outputFormat = {};\n> +\toutputFormat.width = cfg.width;\n> +\toutputFormat.height = cfg.height;\n> +\toutputFormat.fourcc = V4L2_PIX_FMT_NV12;\n> +\toutputFormat.planesCount = 2;\n> +\n> +\tret = video_->setFormat(&outputFormat);\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tif (outputFormat.width != cfg.width ||\n> +\t    outputFormat.height != cfg.height ||\n> +\t    outputFormat.fourcc != V4L2_PIX_FMT_NV12) {\n> +\t\tLOG(RkISP1, Error)\n> +\t\t\t<< \"Unable to configure capture in \" << cfg.width\n> +\t\t\t<< \"x\" << cfg.height << \"-NV12\";\n\nnit: We have so far printed the numeric code for the pixel formats.\n\nThat's basically the only comment I have on this version, so please\nadd my:\nReviewed-by: Jacopo Mondi <jacopo@jmondi.org>\n\nThanks\n  j\n\n> +\t\treturn -EINVAL;\n> +\t}\n> +\n> +\treturn 0;\n> +}\n> +\n> +int PipelineHandlerRkISP1::allocateBuffers(Camera *camera,\n> +\t\t\t\t\t   const std::set<Stream *> &streams)\n> +{\n> +\tStream *stream = *streams.begin();\n> +\treturn video_->exportBuffers(&stream->bufferPool());\n> +}\n> +\n> +int PipelineHandlerRkISP1::freeBuffers(Camera *camera,\n> +\t\t\t\t       const std::set<Stream *> &streams)\n> +{\n> +\tif (video_->releaseBuffers())\n> +\t\tLOG(RkISP1, Error) << \"Failed to release buffers\";\n> +\n> +\treturn 0;\n> +}\n> +\n> +int PipelineHandlerRkISP1::start(Camera *camera)\n> +{\n> +\tint ret;\n> +\n> +\tret = video_->streamOn();\n> +\tif (ret)\n> +\t\tLOG(RkISP1, Error)\n> +\t\t\t<< \"Failed to start camera \" << camera->name();\n> +\n> +\tactiveCamera_ = camera;\n> +\n> +\treturn ret;\n> +}\n> +\n> +void PipelineHandlerRkISP1::stop(Camera *camera)\n> +{\n> +\tint ret;\n> +\n> +\tret = video_->streamOff();\n> +\tif (ret)\n> +\t\tLOG(RkISP1, Warning)\n> +\t\t\t<< \"Failed to stop camera \" << camera->name();\n> +\n> +\tPipelineHandler::stop(camera);\n> +\n> +\tactiveCamera_ = nullptr;\n> +}\n> +\n> +int PipelineHandlerRkISP1::queueRequest(Camera *camera, Request *request)\n> +{\n> +\tRkISP1CameraData *data = cameraData(camera);\n> +\tStream *stream = &data->stream_;\n> +\n> +\tBuffer *buffer = request->findBuffer(stream);\n> +\tif (!buffer) {\n> +\t\tLOG(RkISP1, Error)\n> +\t\t\t<< \"Attempt to queue request with invalid stream\";\n> +\t\treturn -ENOENT;\n> +\t}\n> +\n> +\tint ret = video_->queueBuffer(buffer);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tPipelineHandler::queueRequest(camera, request);\n> +\n> +\treturn 0;\n> +}\n> +\n> +/* -----------------------------------------------------------------------------\n> + * Match and Setup\n> + */\n> +\n> +int PipelineHandlerRkISP1::initLinks()\n> +{\n> +\tMediaLink *link;\n> +\tint ret;\n> +\n> +\tret = media_->disableLinks();\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tlink = media_->link(\"rockchip-sy-mipi-dphy\", 1, \"rkisp1-isp-subdev\", 0);\n> +\tif (!link)\n> +\t\treturn -ENODEV;\n> +\n> +\tret = link->setEnabled(true);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\tlink = media_->link(\"rkisp1-isp-subdev\", 2, \"rkisp1_mainpath\", 0);\n> +\tif (!link)\n> +\t\treturn -ENODEV;\n> +\n> +\tret = link->setEnabled(true);\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\treturn 0;\n> +}\n> +\n> +int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor)\n> +{\n> +\tint ret;\n> +\n> +\tstd::unique_ptr<RkISP1CameraData> data =\n> +\t\tutils::make_unique<RkISP1CameraData>(this);\n> +\n> +\tdata->sensor_ = new CameraSensor(sensor);\n> +\tret = data->sensor_->init();\n> +\tif (ret)\n> +\t\treturn ret;\n> +\n> +\tstd::set<Stream *> streams{ &data->stream_ };\n> +\tstd::shared_ptr<Camera> camera =\n> +\t\tCamera::create(this, sensor->name(), streams);\n> +\tregisterCamera(std::move(camera), std::move(data));\n> +\n> +\treturn 0;\n> +}\n> +\n> +bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)\n> +{\n> +\tconst MediaPad *pad;\n> +\tint ret;\n> +\n> +\tDeviceMatch dm(\"rkisp1\");\n> +\tdm.add(\"rkisp1-isp-subdev\");\n> +\tdm.add(\"rkisp1_selfpath\");\n> +\tdm.add(\"rkisp1_mainpath\");\n> +\tdm.add(\"rkisp1-statistics\");\n> +\tdm.add(\"rkisp1-input-params\");\n> +\tdm.add(\"rockchip-sy-mipi-dphy\");\n> +\n> +\tmedia_ = enumerator->search(dm);\n> +\tif (!media_)\n> +\t\treturn false;\n> +\n> +\tmedia_->acquire();\n> +\n> +\tret = media_->open();\n> +\tif (ret < 0)\n> +\t\treturn ret;\n> +\n> +\t/* Create the V4L2 subdevices we will need. */\n> +\tdphy_ = V4L2Subdevice::fromEntityName(media_.get(),\n> +\t\t\t\t\t      \"rockchip-sy-mipi-dphy\");\n> +\tret = dphy_->open();\n> +\tif (ret < 0)\n> +\t\tgoto done;\n> +\n> +\tisp_ = V4L2Subdevice::fromEntityName(media_.get(), \"rkisp1-isp-subdev\");\n> +\tret = isp_->open();\n> +\tif (ret < 0)\n> +\t\tgoto done;\n> +\n> +\t/* Locate and open the capture video node. */\n> +\tvideo_ = V4L2Device::fromEntityName(media_.get(), \"rkisp1_mainpath\");\n> +\tret = video_->open();\n> +\tif (ret < 0)\n> +\t\tgoto done;\n> +\n> +\tvideo_->bufferReady.connect(this, &PipelineHandlerRkISP1::bufferReady);\n> +\n> +\t/* Configure default links. */\n> +\tret = initLinks();\n> +\tif (ret < 0) {\n> +\t\tLOG(RkISP1, Error) << \"Failed to setup links\";\n> +\t\tgoto done;\n> +\t}\n> +\n> +\t/*\n> +\t * Enumerate all sensors connected to the CSI-2 receiver and create one\n> +\t * camera instance for each of them.\n> +\t */\n> +\tpad = dphy_->entity()->getPadByIndex(0);\n> +\tif (!pad) {\n> +\t\tret = -EINVAL;\n> +\t\tgoto done;\n> +\t}\n> +\n> +\tfor (MediaLink *link : pad->links())\n> +\t\tcreateCamera(link->source()->entity());\n> +\n> +done:\n> +\tmedia_->close();\n> +\n> +\treturn ret == 0;\n> +}\n> +\n> +/* -----------------------------------------------------------------------------\n> + * Buffer Handling\n> + */\n> +\n> +void PipelineHandlerRkISP1::bufferReady(Buffer *buffer)\n> +{\n> +\tASSERT(activeCamera_);\n> +\n> +\tRkISP1CameraData *data = cameraData(activeCamera_);\n> +\tRequest *request = data->queuedRequests_.front();\n> +\n> +\tcompleteBuffer(activeCamera_, request, buffer);\n> +\tcompleteRequest(activeCamera_, request);\n> +}\n> +\n> +REGISTER_PIPELINE_HANDLER(PipelineHandlerRkISP1);\n> +\n> +} /* namespace libcamera */\n> --\n> Regards,\n>\n> Laurent Pinchart\n>\n> _______________________________________________\n> libcamera-devel mailing list\n> libcamera-devel@lists.libcamera.org\n> https://lists.libcamera.org/listinfo/libcamera-devel","headers":{"Return-Path":"<jacopo@jmondi.org>","Received":["from relay10.mail.gandi.net (relay10.mail.gandi.net\n\t[217.70.178.230])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 38BAF60DC6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 18 Apr 2019 16:54:49 +0200 (CEST)","from uno.localdomain (2-224-242-101.ip172.fastwebnet.it\n\t[2.224.242.101]) (Authenticated sender: jacopo@jmondi.org)\n\tby relay10.mail.gandi.net (Postfix) with ESMTPSA id ABFEF240005;\n\tThu, 18 Apr 2019 14:54:48 +0000 (UTC)"],"Date":"Thu, 18 Apr 2019 16:55:41 +0200","From":"Jacopo Mondi <jacopo@jmondi.org>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190418145541.liz5liaxaijzai43@uno.localdomain>","References":"<20190418141437.14014-1-laurent.pinchart@ideasonboard.com>\n\t<20190418141437.14014-14-laurent.pinchart@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"multipart/signed; micalg=pgp-sha256;\n\tprotocol=\"application/pgp-signature\"; boundary=\"bfax3lfohnpfz66b\"","Content-Disposition":"inline","In-Reply-To":"<20190418141437.14014-14-laurent.pinchart@ideasonboard.com>","User-Agent":"NeoMutt/20180716","Subject":"Re: [libcamera-devel] [PATCH v3 13/13] libcamera: pipeline: Add\n\tRKISP1 pipeline","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Thu, 18 Apr 2019 14:54:49 -0000"}},{"id":1458,"web_url":"https://patchwork.libcamera.org/comment/1458/","msgid":"<20190418151148.GU4806@pendragon.ideasonboard.com>","date":"2019-04-18T15:11:48","subject":"Re: [libcamera-devel] [PATCH v3 13/13] libcamera: pipeline: Add\n\tRKISP1 pipeline","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Jacopo,\n\nOn Thu, Apr 18, 2019 at 04:55:41PM +0200, Jacopo Mondi wrote:\n> On Thu, Apr 18, 2019 at 05:14:37PM +0300, Laurent Pinchart wrote:\n> > The pipeline handler for the Rockchip ISP creates one camera instance\n> > per detected raw Bayer CSI-2 sensor. Parallel sensors and YUV sensors\n> > are not supported yet.\n> >\n> > As the ISP has a single CSI-2 receiver, only one camera can be used at a\n> > time. Mutual exclusion isn't implemented yet.\n> >\n> > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> > Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n> > Tested-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>\n> > ---\n> > Changes since v2:\n> >\n> > - Print the format configured at the sensor output\n> > - Close the media device when match() fails\n> > - Use V4L2Subdevice::fromEntityName() and V4L2Device::fromEntityName()\n> > - Rebased on top of the multi-stream preparation commits\n> >\n> > Changes since v1:\n> >\n> > - Move sensor entity sanity checks to CameraSensor class\n> > - Remove unneeded LOG() message\n> > ---\n> >  src/libcamera/pipeline/meson.build        |   1 +\n> >  src/libcamera/pipeline/rkisp1/meson.build |   3 +\n> >  src/libcamera/pipeline/rkisp1/rkisp1.cpp  | 448 ++++++++++++++++++++++\n> >  3 files changed, 452 insertions(+)\n> >  create mode 100644 src/libcamera/pipeline/rkisp1/meson.build\n> >  create mode 100644 src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> >\n> > diff --git a/src/libcamera/pipeline/meson.build b/src/libcamera/pipeline/meson.build\n> > index 40bb26405b88..0d466225a72e 100644\n> > --- a/src/libcamera/pipeline/meson.build\n> > +++ b/src/libcamera/pipeline/meson.build\n> > @@ -4,3 +4,4 @@ libcamera_sources += files([\n> >  ])\n> >\n> >  subdir('ipu3')\n> > +subdir('rkisp1')\n> > diff --git a/src/libcamera/pipeline/rkisp1/meson.build b/src/libcamera/pipeline/rkisp1/meson.build\n> > new file mode 100644\n> > index 000000000000..f1cc4046b5d0\n> > --- /dev/null\n> > +++ b/src/libcamera/pipeline/rkisp1/meson.build\n> > @@ -0,0 +1,3 @@\n> > +libcamera_sources += files([\n> > +    'rkisp1.cpp',\n> > +])\n> > diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > new file mode 100644\n> > index 000000000000..71faa880db49\n> > --- /dev/null\n> > +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp\n> > @@ -0,0 +1,448 @@\n> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> > +/*\n> > + * Copyright (C) 2019, Google Inc.\n> > + *\n> > + * rkisp1.cpp - Pipeline handler for Rockchip ISP1\n> > + */\n> > +\n> > +#include <iomanip>\n> > +#include <memory>\n> > +#include <vector>\n> > +\n> > +#include <linux/media-bus-format.h>\n> > +\n> > +#include <libcamera/camera.h>\n> > +#include <libcamera/request.h>\n> > +#include <libcamera/stream.h>\n> > +\n> > +#include \"camera_sensor.h\"\n> > +#include \"device_enumerator.h\"\n> > +#include \"log.h\"\n> > +#include \"media_device.h\"\n> > +#include \"pipeline_handler.h\"\n> > +#include \"utils.h\"\n> > +#include \"v4l2_device.h\"\n> > +#include \"v4l2_subdevice.h\"\n> > +\n> > +namespace libcamera {\n> > +\n> > +LOG_DEFINE_CATEGORY(RkISP1)\n> > +\n> > +class PipelineHandlerRkISP1 : public PipelineHandler\n> > +{\n> > +public:\n> > +\tPipelineHandlerRkISP1(CameraManager *manager);\n> > +\t~PipelineHandlerRkISP1();\n> > +\n> > +\tCameraConfiguration streamConfiguration(Camera *camera,\n> > +\t\tconst std::vector<StreamUsage> &usages) override;\n> > +\tint configureStreams(Camera *camera,\n> > +\t\tconst CameraConfiguration &config) override;\n> > +\n> > +\tint allocateBuffers(Camera *camera,\n> > +\t\tconst std::set<Stream *> &streams) override;\n> > +\tint freeBuffers(Camera *camera,\n> > +\t\tconst std::set<Stream *> &streams) override;\n> > +\n> > +\tint start(Camera *camera) override;\n> > +\tvoid stop(Camera *camera) override;\n> > +\n> > +\tint queueRequest(Camera *camera, Request *request) override;\n> > +\n> > +\tbool match(DeviceEnumerator *enumerator) override;\n> > +\n> > +private:\n> > +\tclass RkISP1CameraData : public CameraData\n> > +\t{\n> > +\tpublic:\n> > +\t\tRkISP1CameraData(PipelineHandler *pipe)\n> > +\t\t\t: CameraData(pipe), sensor_(nullptr)\n> > +\t\t{\n> > +\t\t}\n> > +\n> > +\t\t~RkISP1CameraData()\n> > +\t\t{\n> > +\t\t\tdelete sensor_;\n> > +\t\t}\n> > +\n> > +\t\tStream stream_;\n> > +\t\tCameraSensor *sensor_;\n> > +\t};\n> > +\n> > +\tstatic constexpr unsigned int RKISP1_BUFFER_COUNT = 4;\n> > +\n> > +\tRkISP1CameraData *cameraData(const Camera *camera)\n> > +\t{\n> > +\t\treturn static_cast<RkISP1CameraData *>(\n> > +\t\t\tPipelineHandler::cameraData(camera));\n> > +\t}\n> > +\n> > +\tint initLinks();\n> > +\tint createCamera(MediaEntity *sensor);\n> > +\tvoid bufferReady(Buffer *buffer);\n> > +\n> > +\tstd::shared_ptr<MediaDevice> media_;\n> > +\tV4L2Subdevice *dphy_;\n> > +\tV4L2Subdevice *isp_;\n> > +\tV4L2Device *video_;\n> > +\n> > +\tCamera *activeCamera_;\n> > +};\n> > +\n> > +PipelineHandlerRkISP1::PipelineHandlerRkISP1(CameraManager *manager)\n> > +\t: PipelineHandler(manager), dphy_(nullptr), isp_(nullptr),\n> > +\t  video_(nullptr)\n> > +{\n> > +}\n> > +\n> > +PipelineHandlerRkISP1::~PipelineHandlerRkISP1()\n> > +{\n> > +\tdelete video_;\n> > +\tdelete isp_;\n> > +\tdelete dphy_;\n> > +\n> > +\tif (media_)\n> > +\t\tmedia_->release();\n> > +}\n> > +\n> > +/* -----------------------------------------------------------------------------\n> > + * Pipeline Operations\n> > + */\n> > +\n> > +CameraConfiguration PipelineHandlerRkISP1::streamConfiguration(Camera *camera,\n> > +\tconst std::vector<StreamUsage> &usages)\n> > +{\n> > +\tRkISP1CameraData *data = cameraData(camera);\n> > +\tCameraConfiguration configs;\n> > +\tStreamConfiguration config{};\n> > +\n> > +\tconst Size &resolution = data->sensor_->resolution();\n> > +\tconfig.width = resolution.width;\n> > +\tconfig.height = resolution.height;\n> > +\tconfig.pixelFormat = V4L2_PIX_FMT_NV12;\n> > +\tconfig.bufferCount = RKISP1_BUFFER_COUNT;\n> > +\n> > +\tconfigs[&data->stream_] = config;\n> > +\n> > +\tLOG(RkISP1, Debug)\n> > +\t\t<< \"Stream format set to \" << config.width << \"x\"\n> > +\t\t<< config.height << \"-0x\" << std::hex << std::setfill('0')\n> > +\t\t<< std::setw(8) << config.pixelFormat;\n> > +\n> > +\treturn configs;\n> > +}\n> > +\n> > +int PipelineHandlerRkISP1::configureStreams(Camera *camera,\n> > +\t\t\t\t\t    const CameraConfiguration &config)\n> > +{\n> > +\tRkISP1CameraData *data = cameraData(camera);\n> > +\tconst StreamConfiguration &cfg = config[&data->stream_];\n> > +\tCameraSensor *sensor = data->sensor_;\n> > +\tint ret;\n> > +\n> > +\t/* Verify the configuration. */\n> > +\tconst Size &resolution = sensor->resolution();\n> > +\tif (cfg.width > resolution.width ||\n> > +\t    cfg.height > resolution.height) {\n> > +\t\tLOG(RkISP1, Error)\n> > +\t\t\t<< \"Invalid stream size: larger than sensor resolution\";\n> > +\t\treturn -EINVAL;\n> > +\t}\n> > +\n> > +\t/*\n> > +\t * Configure the sensor links: enable the link corresponding to this\n> > +\t * camera and disable all the other sensor links.\n> > +\t */\n> > +\tconst MediaPad *pad = dphy_->entity()->getPadByIndex(0);\n> > +\n> > +\tret = media_->open();\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tfor (MediaLink *link : pad->links()) {\n> > +\t\tbool enable = link->source()->entity() == sensor->entity();\n> > +\n> > +\t\tif (!!(link->flags() & MEDIA_LNK_FL_ENABLED) == enable)\n> > +\t\t\tcontinue;\n> > +\n> > +\t\tLOG(RkISP1, Debug)\n> > +\t\t\t<< (enable ? \"Enabling\" : \"Disabling\")\n> > +\t\t\t<< \" link from sensor '\"\n> > +\t\t\t<< link->source()->entity()->name()\n> > +\t\t\t<< \"' to CSI-2 receiver\";\n> > +\n> > +\t\tret = link->setEnabled(enable);\n> > +\t\tif (ret < 0)\n> > +\t\t\tbreak;\n> > +\t}\n> > +\n> > +\tmedia_->close();\n> > +\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\t/*\n> > +\t * Configure the format on the sensor output and propagate it through\n> > +\t * the pipeline.\n> > +\t */\n> > +\tV4L2SubdeviceFormat format;\n> > +\tformat = sensor->getFormat({ MEDIA_BUS_FMT_SBGGR12_1X12,\n> > +\t\t\t\t     MEDIA_BUS_FMT_SGBRG12_1X12,\n> > +\t\t\t\t     MEDIA_BUS_FMT_SGRBG12_1X12,\n> > +\t\t\t\t     MEDIA_BUS_FMT_SRGGB12_1X12,\n> > +\t\t\t\t     MEDIA_BUS_FMT_SBGGR10_1X10,\n> > +\t\t\t\t     MEDIA_BUS_FMT_SGBRG10_1X10,\n> > +\t\t\t\t     MEDIA_BUS_FMT_SGRBG10_1X10,\n> > +\t\t\t\t     MEDIA_BUS_FMT_SRGGB10_1X10,\n> > +\t\t\t\t     MEDIA_BUS_FMT_SBGGR8_1X8,\n> > +\t\t\t\t     MEDIA_BUS_FMT_SGBRG8_1X8,\n> > +\t\t\t\t     MEDIA_BUS_FMT_SGRBG8_1X8,\n> > +\t\t\t\t     MEDIA_BUS_FMT_SRGGB8_1X8 },\n> > +\t\t\t\t   Size(cfg.width, cfg.height));\n> > +\n> > +\tLOG(RkISP1, Debug) << \"Configuring sensor with \" << format.toString();\n> > +\n> > +\tret = sensor->setFormat(&format);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tLOG(RkISP1, Debug) << \"Sensor configured with \" << format.toString();\n> > +\n> > +\tret = dphy_->setFormat(0, &format);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tret = dphy_->getFormat(1, &format);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tret = isp_->setFormat(0, &format);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tV4L2DeviceFormat outputFormat = {};\n> > +\toutputFormat.width = cfg.width;\n> > +\toutputFormat.height = cfg.height;\n> > +\toutputFormat.fourcc = V4L2_PIX_FMT_NV12;\n> > +\toutputFormat.planesCount = 2;\n> > +\n> > +\tret = video_->setFormat(&outputFormat);\n> > +\tif (ret)\n> > +\t\treturn ret;\n> > +\n> > +\tif (outputFormat.width != cfg.width ||\n> > +\t    outputFormat.height != cfg.height ||\n> > +\t    outputFormat.fourcc != V4L2_PIX_FMT_NV12) {\n> > +\t\tLOG(RkISP1, Error)\n> > +\t\t\t<< \"Unable to configure capture in \" << cfg.width\n> > +\t\t\t<< \"x\" << cfg.height << \"-NV12\";\n> \n> nit: We have so far printed the numeric code for the pixel formats.\n\nLet's fix that :-) But OK, for consistency I'll change this.\n\n> That's basically the only comment I have on this version, so please\n> add my:\n> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>\n> \n> > +\t\treturn -EINVAL;\n> > +\t}\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +int PipelineHandlerRkISP1::allocateBuffers(Camera *camera,\n> > +\t\t\t\t\t   const std::set<Stream *> &streams)\n> > +{\n> > +\tStream *stream = *streams.begin();\n> > +\treturn video_->exportBuffers(&stream->bufferPool());\n> > +}\n> > +\n> > +int PipelineHandlerRkISP1::freeBuffers(Camera *camera,\n> > +\t\t\t\t       const std::set<Stream *> &streams)\n> > +{\n> > +\tif (video_->releaseBuffers())\n> > +\t\tLOG(RkISP1, Error) << \"Failed to release buffers\";\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +int PipelineHandlerRkISP1::start(Camera *camera)\n> > +{\n> > +\tint ret;\n> > +\n> > +\tret = video_->streamOn();\n> > +\tif (ret)\n> > +\t\tLOG(RkISP1, Error)\n> > +\t\t\t<< \"Failed to start camera \" << camera->name();\n> > +\n> > +\tactiveCamera_ = camera;\n> > +\n> > +\treturn ret;\n> > +}\n> > +\n> > +void PipelineHandlerRkISP1::stop(Camera *camera)\n> > +{\n> > +\tint ret;\n> > +\n> > +\tret = video_->streamOff();\n> > +\tif (ret)\n> > +\t\tLOG(RkISP1, Warning)\n> > +\t\t\t<< \"Failed to stop camera \" << camera->name();\n> > +\n> > +\tPipelineHandler::stop(camera);\n> > +\n> > +\tactiveCamera_ = nullptr;\n> > +}\n> > +\n> > +int PipelineHandlerRkISP1::queueRequest(Camera *camera, Request *request)\n> > +{\n> > +\tRkISP1CameraData *data = cameraData(camera);\n> > +\tStream *stream = &data->stream_;\n> > +\n> > +\tBuffer *buffer = request->findBuffer(stream);\n> > +\tif (!buffer) {\n> > +\t\tLOG(RkISP1, Error)\n> > +\t\t\t<< \"Attempt to queue request with invalid stream\";\n> > +\t\treturn -ENOENT;\n> > +\t}\n> > +\n> > +\tint ret = video_->queueBuffer(buffer);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tPipelineHandler::queueRequest(camera, request);\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +/* -----------------------------------------------------------------------------\n> > + * Match and Setup\n> > + */\n> > +\n> > +int PipelineHandlerRkISP1::initLinks()\n> > +{\n> > +\tMediaLink *link;\n> > +\tint ret;\n> > +\n> > +\tret = media_->disableLinks();\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tlink = media_->link(\"rockchip-sy-mipi-dphy\", 1, \"rkisp1-isp-subdev\", 0);\n> > +\tif (!link)\n> > +\t\treturn -ENODEV;\n> > +\n> > +\tret = link->setEnabled(true);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\tlink = media_->link(\"rkisp1-isp-subdev\", 2, \"rkisp1_mainpath\", 0);\n> > +\tif (!link)\n> > +\t\treturn -ENODEV;\n> > +\n> > +\tret = link->setEnabled(true);\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +int PipelineHandlerRkISP1::createCamera(MediaEntity *sensor)\n> > +{\n> > +\tint ret;\n> > +\n> > +\tstd::unique_ptr<RkISP1CameraData> data =\n> > +\t\tutils::make_unique<RkISP1CameraData>(this);\n> > +\n> > +\tdata->sensor_ = new CameraSensor(sensor);\n> > +\tret = data->sensor_->init();\n> > +\tif (ret)\n> > +\t\treturn ret;\n> > +\n> > +\tstd::set<Stream *> streams{ &data->stream_ };\n> > +\tstd::shared_ptr<Camera> camera =\n> > +\t\tCamera::create(this, sensor->name(), streams);\n> > +\tregisterCamera(std::move(camera), std::move(data));\n> > +\n> > +\treturn 0;\n> > +}\n> > +\n> > +bool PipelineHandlerRkISP1::match(DeviceEnumerator *enumerator)\n> > +{\n> > +\tconst MediaPad *pad;\n> > +\tint ret;\n> > +\n> > +\tDeviceMatch dm(\"rkisp1\");\n> > +\tdm.add(\"rkisp1-isp-subdev\");\n> > +\tdm.add(\"rkisp1_selfpath\");\n> > +\tdm.add(\"rkisp1_mainpath\");\n> > +\tdm.add(\"rkisp1-statistics\");\n> > +\tdm.add(\"rkisp1-input-params\");\n> > +\tdm.add(\"rockchip-sy-mipi-dphy\");\n> > +\n> > +\tmedia_ = enumerator->search(dm);\n> > +\tif (!media_)\n> > +\t\treturn false;\n> > +\n> > +\tmedia_->acquire();\n> > +\n> > +\tret = media_->open();\n> > +\tif (ret < 0)\n> > +\t\treturn ret;\n> > +\n> > +\t/* Create the V4L2 subdevices we will need. */\n> > +\tdphy_ = V4L2Subdevice::fromEntityName(media_.get(),\n> > +\t\t\t\t\t      \"rockchip-sy-mipi-dphy\");\n> > +\tret = dphy_->open();\n> > +\tif (ret < 0)\n> > +\t\tgoto done;\n> > +\n> > +\tisp_ = V4L2Subdevice::fromEntityName(media_.get(), \"rkisp1-isp-subdev\");\n> > +\tret = isp_->open();\n> > +\tif (ret < 0)\n> > +\t\tgoto done;\n> > +\n> > +\t/* Locate and open the capture video node. */\n> > +\tvideo_ = V4L2Device::fromEntityName(media_.get(), \"rkisp1_mainpath\");\n> > +\tret = video_->open();\n> > +\tif (ret < 0)\n> > +\t\tgoto done;\n> > +\n> > +\tvideo_->bufferReady.connect(this, &PipelineHandlerRkISP1::bufferReady);\n> > +\n> > +\t/* Configure default links. */\n> > +\tret = initLinks();\n> > +\tif (ret < 0) {\n> > +\t\tLOG(RkISP1, Error) << \"Failed to setup links\";\n> > +\t\tgoto done;\n> > +\t}\n> > +\n> > +\t/*\n> > +\t * Enumerate all sensors connected to the CSI-2 receiver and create one\n> > +\t * camera instance for each of them.\n> > +\t */\n> > +\tpad = dphy_->entity()->getPadByIndex(0);\n> > +\tif (!pad) {\n> > +\t\tret = -EINVAL;\n> > +\t\tgoto done;\n> > +\t}\n> > +\n> > +\tfor (MediaLink *link : pad->links())\n> > +\t\tcreateCamera(link->source()->entity());\n> > +\n> > +done:\n> > +\tmedia_->close();\n> > +\n> > +\treturn ret == 0;\n> > +}\n> > +\n> > +/* -----------------------------------------------------------------------------\n> > + * Buffer Handling\n> > + */\n> > +\n> > +void PipelineHandlerRkISP1::bufferReady(Buffer *buffer)\n> > +{\n> > +\tASSERT(activeCamera_);\n> > +\n> > +\tRkISP1CameraData *data = cameraData(activeCamera_);\n> > +\tRequest *request = data->queuedRequests_.front();\n> > +\n> > +\tcompleteBuffer(activeCamera_, request, buffer);\n> > +\tcompleteRequest(activeCamera_, request);\n> > +}\n> > +\n> > +REGISTER_PIPELINE_HANDLER(PipelineHandlerRkISP1);\n> > +\n> > +} /* namespace libcamera */","headers":{"Return-Path":"<laurent.pinchart@ideasonboard.com>","Received":["from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 1953A60DC6\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 18 Apr 2019 17:11:58 +0200 (CEST)","from pendragon.ideasonboard.com (81-175-216-236.bb.dnainternet.fi\n\t[81.175.216.236])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 74694333;\n\tThu, 18 Apr 2019 17:11:56 +0200 (CEST)"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1555600316;\n\tbh=R9gp6cK92TxH9PtH5pKNeIVYqVdZTXsFrBs+XnJ14cA=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=dPsFW0/lnJJhGFicZU8RBs8gWQ4r7UsVlOyfaApSP3OP9LwAzqP9eeUspT55tMtoz\n\tkHaguf3J6B4OLyM1ND/3oH9EmXvx+qN0mqwPz/pcJlQAbAEiXiBpSAm1tXYegx3B6n\n\tYfwwN/1ZLRe/oiaNPkHo4ytVwJN7ixSVJOax6J38=","Date":"Thu, 18 Apr 2019 18:11:48 +0300","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Jacopo Mondi <jacopo@jmondi.org>","Cc":"libcamera-devel@lists.libcamera.org","Message-ID":"<20190418151148.GU4806@pendragon.ideasonboard.com>","References":"<20190418141437.14014-1-laurent.pinchart@ideasonboard.com>\n\t<20190418141437.14014-14-laurent.pinchart@ideasonboard.com>\n\t<20190418145541.liz5liaxaijzai43@uno.localdomain>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","Content-Transfer-Encoding":"8bit","In-Reply-To":"<20190418145541.liz5liaxaijzai43@uno.localdomain>","User-Agent":"Mutt/1.10.1 (2018-07-13)","Subject":"Re: [libcamera-devel] [PATCH v3 13/13] libcamera: pipeline: Add\n\tRKISP1 pipeline","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.23","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","X-List-Received-Date":"Thu, 18 Apr 2019 15:11:58 -0000"}}]