blob: edb9d20a005cb19bf5e03842f6c23b42e4856233 [file] [log] [blame] [edit]
/* Copyright 2023 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/* NOTE: This code merely serves as an example usage of the class hierarchy,
* and has not been optimised for style or quality.
*
* For each sensor on a media controller, attempt to find and configure
* a route to a /dev/videoX device using a depth-first search.
*
* This assumes that any free links can be used equally well, and hence
* works best on homogeneous devices like IPU6.
*
* This is a remnant of the v1 tool:
* https://chromium-review.googlesource.com/c/chromiumos/platform2/+/4055245
*/
// NOLINTNEXTLINE(build/include)
#include "tools/mctk/routing.h"
#include <linux/media.h>
#include <linux/types.h>
#include <linux/v4l2-mediabus.h>
#include <linux/videodev2.h>
#include <stdio.h>
#include <string.h>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "tools/mctk/debug.h"
#include "tools/mctk/mcdev.h"
// #define ROUTING_PRINT_EVERY_STEP
namespace {
__u32 SubFmtToV4lFmt(__u32 mbus_code) {
static const __u32 lut_bayer[] = {
V4L2_PIX_FMT_SBGGR8, /* MEDIA_BUS_FMT_SBGGR8_1X8 0x3001 */
V4L2_PIX_FMT_SGRBG8, /* MEDIA_BUS_FMT_SGRBG8_1X8 0x3002 */
V4L2_PIX_FMT_SBGGR10, /* MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE 0x3003 */
V4L2_PIX_FMT_SBGGR10, /* MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE 0x3004 */
V4L2_PIX_FMT_SBGGR10, /* MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_BE 0x3005 */
V4L2_PIX_FMT_SBGGR10, /* MEDIA_BUS_FMT_SBGGR10_2X8_PADLO_LE 0x3006 */
V4L2_PIX_FMT_SBGGR10, /* MEDIA_BUS_FMT_SBGGR10_1X10 0x3007 */
V4L2_PIX_FMT_SBGGR12, /* MEDIA_BUS_FMT_SBGGR12_1X12 0x3008 */
V4L2_PIX_FMT_SGRBG10DPCM8, /* MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8 0x3009 */
V4L2_PIX_FMT_SGRBG10, /* MEDIA_BUS_FMT_SGRBG10_1X10 0x300a */
V4L2_PIX_FMT_SBGGR10, /* MEDIA_BUS_FMT_SBGGR10_DPCM8_1X8 0x300b */
V4L2_PIX_FMT_SGBRG10, /* MEDIA_BUS_FMT_SGBRG10_DPCM8_1X8 0x300c */
V4L2_PIX_FMT_SRGGB10, /* MEDIA_BUS_FMT_SRGGB10_DPCM8_1X8 0x300d */
V4L2_PIX_FMT_SGBRG10, /* MEDIA_BUS_FMT_SGBRG10_1X10 0x300e */
V4L2_PIX_FMT_SRGGB10, /* MEDIA_BUS_FMT_SRGGB10_1X10 0x300f */
V4L2_PIX_FMT_SGBRG12, /* MEDIA_BUS_FMT_SGBRG12_1X12 0x3010 */
V4L2_PIX_FMT_SGRBG12, /* MEDIA_BUS_FMT_SGRBG12_1X12 0x3011 */
V4L2_PIX_FMT_SRGGB12, /* MEDIA_BUS_FMT_SRGGB12_1X12 0x3012 */
V4L2_PIX_FMT_SGBRG8, /* MEDIA_BUS_FMT_SGBRG8_1X8 0x3013 */
V4L2_PIX_FMT_SRGGB8, /* MEDIA_BUS_FMT_SRGGB8_1X8 0x3014 */
V4L2_PIX_FMT_SBGGR10ALAW8, /* MEDIA_BUS_FMT_SBGGR10_ALAW8_1X8 0x3015 */
V4L2_PIX_FMT_SGBRG10ALAW8, /* MEDIA_BUS_FMT_SGBRG10_ALAW8_1X8 0x3016 */
V4L2_PIX_FMT_SGRBG10ALAW8, /* MEDIA_BUS_FMT_SGRBG10_ALAW8_1X8 0x3017 */
V4L2_PIX_FMT_SRGGB10ALAW8, /* MEDIA_BUS_FMT_SRGGB10_ALAW8_1X8 0x3018 */
/* V4L2_PIX_FMT_SBGGR14 defined in Linux v4.19 */
v4l2_fourcc('B', 'G', '1', '4'), /* MEDIA_BUS_FMT_SBGGR14_1X14 0x3019 */
/* V4L2_PIX_FMT_SGBRG14 defined in Linux v4.19 */
v4l2_fourcc('G', 'B', '1', '4'), /* MEDIA_BUS_FMT_SGBRG14_1X14 0x301a */
/* V4L2_PIX_FMT_SGRBG14 defined in Linux v4.19 */
v4l2_fourcc('G', 'R', '1', '4'), /* MEDIA_BUS_FMT_SGRBG14_1X14 0x301b */
/* V4L2_PIX_FMT_SRGGB14 defined in Linux v4.19 */
v4l2_fourcc('R', 'G', '1', '4'), /* MEDIA_BUS_FMT_SRGGB14_1X14 0x301c */
V4L2_PIX_FMT_SBGGR16, /* MEDIA_BUS_FMT_SBGGR16_1X16 0x301d */
V4L2_PIX_FMT_SGBRG16, /* MEDIA_BUS_FMT_SGBRG16_1X16 0x301e */
V4L2_PIX_FMT_SGRBG16, /* MEDIA_BUS_FMT_SGRBG16_1X16 0x301f */
V4L2_PIX_FMT_SRGGB16, /* MEDIA_BUS_FMT_SRGGB16_1X16 0x3020 */
};
if (mbus_code >= MEDIA_BUS_FMT_SBGGR8_1X8 &&
mbus_code <= MEDIA_BUS_FMT_SRGGB16_1X16) {
return lut_bayer[mbus_code - MEDIA_BUS_FMT_SBGGR8_1X8];
}
return 0;
}
void AnydevSetFormatFromSubfmt(V4lMcPad& pad,
struct v4l2_mbus_framefmt& subfmt) {
/* Try setting a "crop" selection showing the full frame.
* We don't fail here, as the driver may work as intended
* even if it doesn't support these options.
*/
struct v4l2_rect crop = {
.left = 0,
.top = 0,
.width = subfmt.width,
.height = subfmt.height,
};
if (pad.entity_.desc_.type == MEDIA_ENT_F_IO_V4L) {
/* This pad is a V4L maindev /dev/videoX */
struct v4l2_pix_format pix = {};
pix.width = subfmt.width;
pix.height = subfmt.height;
pix.pixelformat = SubFmtToV4lFmt(subfmt.code);
pix.field = V4L2_FIELD_NONE;
/* IPU6 may or may not work without proper bytesperline and sizeimage */
// pix.bytesperline = 0;
// pix.sizeimage = 0;
pix.colorspace = subfmt.colorspace;
struct v4l2_pix_format_mplane pix_mp = {};
pix_mp.width = subfmt.width;
pix_mp.height = subfmt.height;
pix_mp.pixelformat = SubFmtToV4lFmt(subfmt.code);
pix_mp.field = V4L2_FIELD_NONE;
pix_mp.colorspace = subfmt.colorspace;
/* IPU6 may or may not work without proper bytesperline and sizeimage */
// pix_mp.plane_fmt[0].sizeimage = 0;
// pix_mp.plane_fmt[0].bytesperline = 0;
/* Did the format conversion fail? */
if (!pix.pixelformat) {
MCTK_ERR("Routing: Format conversion from subfmt to V4L fmt failed.");
return;
}
/* Just set the formats as-is */
pad.entity_.SetFmtVideoCapture(pix);
pad.entity_.SetFmtVideoCaptureMplane(pix_mp);
/* Set the full-frame crop selection */
pad.entity_.SetSelection(V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
V4L2_SEL_TGT_CROP, crop);
} else {
/* This pad is a V4L subdev /dev/v4l-subdevX */
pad.SetFmt(subfmt);
/* Set the full-frame crop selection */
pad.SetSelection(V4L2_SEL_TGT_CROP, crop);
}
}
/* Recursively try to find a route from a given entity to a V4L device.
* This currently assumes an Intel IPU6 like architecture:
* -> Directed graph
* -> No cycles
* -> At each level, all links are equal.
* -> At each level, we can pick any unused entity.
* -> There are no immutable links.
* Even on IPU6, we have reduced the choices a bit by excluding entities
* that break these assumptions.
*/
bool RouteFrom(V4lMcDev& mcdev,
V4lMcEntity& entity,
std::vector<V4lMcLink*>& route) {
#ifdef ROUTING_PRINT_EVERY_STEP
fprintf(stderr, "%s: Looking at: %s\n", __func__, entity->desc_.name);
#endif
/* IPU6 HACK:
* Ignore Intel IPU6 CSI-2 capture.
* Let's assume these devices are numbered in single digits only,
* and match the string parts the simple way.
*/
if (!memcmp(entity.desc_.name, "Intel IPU6 CSI-2 ", 17) &
!memcmp(&entity.desc_.name[18], " capture", 9))
return false;
/* IPU6 HACK:
* Ignore this unknown device.
* We want the "BE SOC" targets for now.
*/
if (!strcmp(entity.desc_.name, "Intel IPU6 CSI2 BE"))
return false;
/* If there is already en entity connected to us, backtrack.
* Our caller will try the next entity.
*/
for (auto link : mcdev.all_links_) {
if (!link->IsDataLink())
/* We only look at data links */
continue;
if (&link->sink_->entity_ != &entity)
continue;
if (link->IsImmutable())
/* Ignore these for now.
* This is likely to be seen in devices with more complex routing
* requirements, and never occurs in IPU6.
*/
continue;
/* Something is already connected to this entity,
* so drop it from routing.
*/
if (link->IsEnabled())
return false;
}
if (entity.desc_.type == MEDIA_ENT_F_IO_V4L)
/* Done, we've found a route! */
return true;
/* We're not the end of the line, and we're yet unconnected.
* Try all outgoing links (they are all in the entity's array).
*
* link->src->entity_ == entity
*/
for (auto& link : entity.links_) {
if (!link->IsDataLink())
/* We only look at data links */
continue;
if (link->IsImmutable())
/* Ignore these for now.
* This is likely to be seen in devices with more complex routing
* requirements, and never occurs in IPU6.
*/
continue;
/* If this is true, there is a path to a V4L video device. */
if (RouteFrom(mcdev, link->sink_->entity_, route)) {
route.insert(route.begin(), link.get());
return true;
}
}
return false;
}
} // namespace
void V4lMcRouteSensors(V4lMcDev& mcdev) {
/* IPU6 HACK:
* Warn if this is not run on IPU6
*/
if (strcmp(mcdev.info_.driver, "intel-ipu6-isys") ||
strcmp(mcdev.info_.model, "ipu6"))
MCTK_ERR("This is not an IPU6 device. Assumptions may not hold.");
/* First, find a camera */
for (auto& sensor_entity : mcdev.entities_) {
std::vector<V4lMcLink*> route;
V4lMcPad* camera_pad;
struct v4l2_mbus_framefmt subfmt;
if (sensor_entity->desc_.type != MEDIA_ENT_T_V4L2_SUBDEV_SENSOR)
/* synonym: MEDIA_ENT_F_CAM_SENSOR */
continue;
/* Second, route it to whatever output we can */
if (!(RouteFrom(mcdev, *sensor_entity, route))) {
fprintf(stdout, "NO ROUTE FOR: %s\n", sensor_entity->desc_.name);
continue;
}
/* Get camera's format */
camera_pad = route.front()->src_;
MCTK_ASSERT(camera_pad->subdev_.fmt);
subfmt = *camera_pad->subdev_.fmt;
/* Set all links and video formats */
for (auto* hop : route) {
AnydevSetFormatFromSubfmt(*hop->src_, subfmt);
AnydevSetFormatFromSubfmt(*hop->sink_, subfmt);
hop->SetEnable(true);
}
/* IPU6 HACK:
* Disable Intel IPU6 compression
*/
auto* cc = route.back()->sink_->entity_.ControlById(0x00981983);
if (cc)
cc->Set<__s32>(0);
/* Print the routing */
fprintf(stdout, "Routed: %s = %s\n",
route.back()->sink_->entity_.devpath_.c_str(),
sensor_entity->desc_.name);
}
}