blob: dcab40b8e3c7fa4516f822a8bf67d16b9f4572cd [file] [log] [blame]
/*
V4L2 API compliance input/output configuration ioctl tests.
Copyright (C) 2011 Hans Verkuil <hverkuil@xs4all.nl>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
*/
#include <map>
#include <set>
#include <sys/types.h>
#include "v4l2-compliance.h"
static int checkStd(struct node *node, bool has_std, v4l2_std_id mask, bool is_input)
{
v4l2_std_id std_mask = 0;
v4l2_std_id std = 0;
struct v4l2_standard enumstd;
unsigned i;
int ret;
ret = doioctl(node, VIDIOC_G_STD, &std);
if (ret && has_std)
return fail("STD cap set, but could not get standard\n");
if (!ret && !has_std)
return fail("STD cap not set, but could still get a standard\n");
if (ret != ENOTTY && ret != ENODATA && !has_std)
return fail("STD cap not set, but got wrong error code (%d)\n", ret);
if (!ret && has_std) {
if (std & ~mask)
warn("current standard is invalid according to the standard mask\n");
if (std == 0)
return fail("Standard == 0?!\n");
if (std & V4L2_STD_ATSC)
return fail("Current standard contains ATSC standards. This is no longer supported\n");
}
ret = doioctl(node, VIDIOC_S_STD, &std);
if (ret && has_std)
return fail("STD cap set, but could not set standard\n");
if (!ret && !has_std)
return fail("STD cap not set, but could still set a standard\n");
std = V4L2_STD_ATSC;
ret = doioctl(node, VIDIOC_S_STD, &std);
if (ret != ENODATA && ret != EINVAL && ret != ENOTTY)
return fail("could set standard to ATSC, which is not supported anymore\n");
for (i = 0; ; i++) {
memset(&enumstd, 0xff, sizeof(enumstd));
enumstd.index = i;
ret = doioctl(node, VIDIOC_ENUMSTD, &enumstd);
if (ret)
break;
std_mask |= enumstd.id;
if (check_ustring(enumstd.name, sizeof(enumstd.name)))
return fail("invalid standard name\n");
if (check_0(enumstd.reserved, sizeof(enumstd.reserved)))
return fail("reserved not zeroed\n");
if (enumstd.framelines == 0)
return fail("framelines not filled\n");
if (enumstd.framelines != 625 && enumstd.framelines != 525)
warn("strange framelines value %d for standard %08llx\n",
enumstd.framelines, enumstd.id);
if (enumstd.frameperiod.numerator == 0 || enumstd.frameperiod.denominator == 0)
return fail("frameperiod is not filled\n");
if (enumstd.index != i)
return fail("index changed!\n");
if (enumstd.id == 0)
return fail("empty standard returned\n");
}
if (i == 0 && has_std)
return fail("STD cap set, but no standards can be enumerated\n");
if (i && !has_std)
return fail("STD cap was not set, but standards can be enumerated\n");
if (ret != ENOTTY && ret != ENODATA && !has_std)
return fail("STD cap not set, but got wrong error code for enumeration (%d)\n", ret);
if (std_mask & V4L2_STD_ATSC)
return fail("STD mask contains ATSC standards. This is no longer supported\n");
if (has_std && std_mask != mask)
return fail("the union of ENUMSTD does not match the standard mask (%llx != %llx)\n",
std_mask, mask);
ret = doioctl(node, VIDIOC_QUERYSTD, &std);
if (!ret && !has_std)
return fail("STD cap was not set, but could still query standard\n");
if (ret != ENOTTY && ret != ENODATA && !has_std)
return fail("STD cap not set, but got wrong error code for query (%d)\n", ret);
if (ret != ENOTTY && !is_input)
return fail("this is an output, but could still query standard\n");
if (!ret && is_input && (std & ~std_mask))
return fail("QUERYSTD gives back an unsupported standard\n");
return 0;
}
int testStd(struct node *node)
{
int ret;
unsigned i, o;
bool has_std = false;
for (i = 0; i < node->inputs; i++) {
struct v4l2_input input;
input.index = i;
ret = doioctl(node, VIDIOC_ENUMINPUT, &input);
if (ret)
return fail("could not enumerate input %d?!\n", i);
ret = doioctl(node, VIDIOC_S_INPUT, &input.index);
if (ret)
return fail("could not select input %d.\n", i);
if (input.capabilities & V4L2_IN_CAP_STD)
has_std = true;
if (checkStd(node, input.capabilities & V4L2_IN_CAP_STD, input.std, true))
return fail("STD failed for input %d.\n", i);
}
for (o = 0; o < node->outputs; o++) {
struct v4l2_output output;
output.index = o;
ret = doioctl(node, VIDIOC_ENUMOUTPUT, &output);
if (ret)
return fail("could not enumerate output %d?!\n", o);
ret = doioctl(node, VIDIOC_S_OUTPUT, &output.index);
if (ret)
return fail("could not select output %d.\n", o);
if (output.capabilities & V4L2_OUT_CAP_STD)
has_std = true;
if (checkStd(node, output.capabilities & V4L2_OUT_CAP_STD, output.std, false))
return fail("STD failed for output %d.\n", o);
}
return has_std ? 0 : ENOTTY;
}
static int checkTimings(struct node *node, bool has_timings, bool is_input)
{
struct v4l2_enum_dv_timings enumtimings;
struct v4l2_dv_timings timings;
struct v4l2_format fmt = { 0 };
bool is_mplane = node->g_caps() & (V4L2_CAP_VIDEO_CAPTURE_MPLANE |
V4L2_CAP_VIDEO_OUTPUT_MPLANE |
V4L2_CAP_VIDEO_M2M_MPLANE);
unsigned type;
unsigned i;
int ret;
memset(&timings, 0xff, sizeof(timings));
if (node->can_capture)
type = is_mplane ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : V4L2_BUF_TYPE_VIDEO_CAPTURE;
else
type = is_mplane ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE : V4L2_BUF_TYPE_VIDEO_OUTPUT;
ret = doioctl(node, VIDIOC_G_DV_TIMINGS, &timings);
if (ret && has_timings)
return fail("TIMINGS cap set, but could not get current timings\n");
if (!ret && !has_timings)
return fail("TIMINGS cap not set, but could still get timings\n");
if (ret != ENOTTY && ret != ENODATA && !has_timings)
return fail("TIMINGS cap not set, but got wrong error code (%d)\n", ret);
if (!ret) {
fail_on_test(timings.type != V4L2_DV_BT_656_1120);
fail_on_test(check_0(timings.bt.reserved, sizeof(timings.bt.reserved)));
}
for (i = 0; ; i++) {
memset(&enumtimings, 0xff, sizeof(enumtimings));
enumtimings.index = i;
enumtimings.pad = 0;
ret = doioctl(node, VIDIOC_ENUM_DV_TIMINGS, &enumtimings);
if (ret)
break;
if (check_0(enumtimings.reserved, sizeof(enumtimings.reserved)))
return fail("reserved not zeroed\n");
fail_on_test(check_0(enumtimings.timings.bt.reserved,
sizeof(enumtimings.timings.bt.reserved)));
if (enumtimings.index != i)
return fail("index changed!\n");
memset(enumtimings.timings.bt.reserved, 0xff,
sizeof(enumtimings.timings.bt.reserved));
fail_on_test(doioctl(node, VIDIOC_S_DV_TIMINGS, &enumtimings.timings));
fail_on_test(check_0(enumtimings.timings.bt.reserved,
sizeof(enumtimings.timings.bt.reserved)));
struct v4l2_dv_timings g_timings;
fail_on_test(doioctl(node, VIDIOC_G_DV_TIMINGS, &g_timings));
fail_on_test(g_timings.bt.width != enumtimings.timings.bt.width);
fail_on_test(g_timings.bt.height != enumtimings.timings.bt.height);
if (node->is_vbi || node->is_meta)
continue;
fmt.type = type;
fail_on_test(doioctl(node, VIDIOC_G_FMT, &fmt));
unsigned field = is_mplane ? fmt.fmt.pix_mp.field : fmt.fmt.pix.field;
if (enumtimings.timings.bt.interlaced)
fail_on_test(field == V4L2_FIELD_NONE);
else
fail_on_test(field != V4L2_FIELD_NONE);
unsigned factor = V4L2_FIELD_HAS_T_OR_B(field) ? 2 : 1;
// Test if the new format after setting new timings is either too small
// or more than 1.5 times the width/height. This allows for some
// adjustments to be made to the format, but still detect if the
// format isn't updated by the driver.
if (is_mplane) {
fail_on_test(fmt.fmt.pix_mp.width < enumtimings.timings.bt.width);
fail_on_test(fmt.fmt.pix_mp.width >= enumtimings.timings.bt.width * 1.5);
fail_on_test(fmt.fmt.pix_mp.height * factor < enumtimings.timings.bt.height);
fail_on_test(fmt.fmt.pix_mp.height * factor >= enumtimings.timings.bt.height * 1.5);
} else {
fail_on_test(fmt.fmt.pix.width < enumtimings.timings.bt.width);
fail_on_test(fmt.fmt.pix.width >= enumtimings.timings.bt.width * 1.5);
fail_on_test(fmt.fmt.pix.height * factor < enumtimings.timings.bt.height);
fail_on_test(fmt.fmt.pix.height * factor >= enumtimings.timings.bt.height * 1.5);
}
}
if (i == 0 && has_timings)
return fail("TIMINGS cap set, but no timings can be enumerated\n");
if (i && !has_timings)
return fail("TIMINGS cap was not set, but timings can be enumerated\n");
if (ret != ENOTTY && ret != ENODATA && !has_timings)
return fail("TIMINGS cap not set, but got wrong error code for enumeration (%d)\n", ret);
if (has_timings)
fail_on_test(doioctl(node, VIDIOC_S_DV_TIMINGS, &timings));
ret = doioctl(node, VIDIOC_QUERY_DV_TIMINGS, &timings);
if (!ret)
fail_on_test(check_0(timings.bt.reserved, sizeof(timings.bt.reserved)));
if (!ret && !has_timings)
return fail("TIMINGS cap was not set, but could still query timings\n");
if (ret != ENOTTY && ret != ENODATA && !has_timings)
return fail("TIMINGS cap not set, but got wrong error code for query (%d)\n", ret);
if (ret != ENOTTY && !is_input)
return fail("this is an output, but could still query timings\n");
return 0;
}
static int checkSubDevEnumTimings(struct node *node, __u32 pad)
{
struct v4l2_enum_dv_timings enumtimings;
struct v4l2_dv_timings timings;
bool found_fmt_mismatch = false;
bool has_timings;
int ret;
memset(&timings, 0xff, sizeof(timings));
ret = doioctl(node, VIDIOC_G_DV_TIMINGS, &timings);
has_timings = ret != ENOTTY;
fail_on_test(has_timings && ret);
if (has_timings) {
fail_on_test(check_0(timings.bt.reserved, sizeof(timings.bt.reserved)));
memset(timings.bt.reserved, 0xff, sizeof(timings.bt.reserved));
fail_on_test(doioctl(node, VIDIOC_S_DV_TIMINGS, &timings));
fail_on_test(check_0(timings.bt.reserved, sizeof(timings.bt.reserved)));
} else {
fail_on_test(doioctl(node, VIDIOC_S_DV_TIMINGS, &timings) != ENOTTY);
fail_on_test(doioctl(node, VIDIOC_QUERY_DV_TIMINGS, &timings) != ENOTTY);
}
for (unsigned i = 0; ; i++) {
memset(&enumtimings, 0xff, sizeof(enumtimings));
enumtimings.index = i;
enumtimings.pad = pad;
ret = doioctl(node, VIDIOC_ENUM_DV_TIMINGS, &enumtimings);
fail_on_test(ret != ENOTTY && !has_timings);
fail_on_test(ret == ENOTTY && has_timings);
if (ret == ENOTTY)
return ret;
fail_on_test(ret && ret != EINVAL);
fail_on_test(ret && !i);
if (ret)
break;
fail_on_test(check_0(enumtimings.reserved, sizeof(enumtimings.reserved)));
fail_on_test(check_0(enumtimings.timings.bt.reserved,
sizeof(enumtimings.timings.bt.reserved)));
fail_on_test(enumtimings.index != i);
fail_on_test(enumtimings.pad != pad);
memset(enumtimings.timings.bt.reserved, 0xff,
sizeof(enumtimings.timings.bt.reserved));
fail_on_test(doioctl(node, VIDIOC_S_DV_TIMINGS, &enumtimings.timings));
fail_on_test(check_0(enumtimings.timings.bt.reserved,
sizeof(enumtimings.timings.bt.reserved)));
struct v4l2_dv_timings g_timings;
fail_on_test(doioctl(node, VIDIOC_G_DV_TIMINGS, &g_timings));
fail_on_test(g_timings.bt.width != enumtimings.timings.bt.width);
fail_on_test(g_timings.bt.height != enumtimings.timings.bt.height);
if (found_fmt_mismatch)
continue;
struct v4l2_subdev_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
// Test if the new format after setting new timings is either too small
// or more than 1.5 times the width/height. This allows for some
// adjustments to be made to the format, but still detect if the
// format isn't updated by the driver.
if (!doioctl(node, VIDIOC_SUBDEV_G_FMT, &fmt)) {
if (V4L2_FIELD_HAS_T_OR_B(fmt.format.field))
fmt.format.height *= 2;
if (fmt.format.width < enumtimings.timings.bt.width) {
warn("Active width for pad 0 is %u, which is less than the timings width of %u\n",
fmt.format.width, enumtimings.timings.bt.width);
found_fmt_mismatch = true;
}
if (fmt.format.width >= enumtimings.timings.bt.width * 1.5) {
warn("Active width for pad 0 is %u, which is more than 1.5 * the timings width of %u\n",
fmt.format.width, enumtimings.timings.bt.width);
found_fmt_mismatch = true;
}
if (fmt.format.height < enumtimings.timings.bt.height) {
warn("Active height for pad 0 is %u, which is less than the timings height of %u\n",
fmt.format.height, enumtimings.timings.bt.height);
found_fmt_mismatch = true;
}
if (fmt.format.height >= enumtimings.timings.bt.height * 1.5) {
warn("Active height for pad 0 is %u, which is more than 1.5 * the timings height of %u\n",
fmt.format.height, enumtimings.timings.bt.height);
found_fmt_mismatch = true;
}
}
}
enumtimings.pad = node->entity.pads;
enumtimings.index = 0;
fail_on_test(doioctl(node, VIDIOC_ENUM_DV_TIMINGS, &enumtimings) != EINVAL);
fail_on_test(doioctl(node, VIDIOC_S_DV_TIMINGS, &timings));
memset(&timings, 0xff, sizeof(timings));
ret = doioctl(node, VIDIOC_QUERY_DV_TIMINGS, &timings);
fail_on_test(ret == ENOTTY);
fail_on_test(ret == ENODATA);
if (!ret || ret == ERANGE)
fail_on_test(check_0(timings.bt.reserved, sizeof(timings.bt.reserved)));
return 0;
}
int testTimings(struct node *node)
{
int ret;
unsigned i, o;
bool has_timings = false;
if (node->is_subdev()) {
for (unsigned pad = 0; pad < node->entity.pads; pad++) {
ret = checkSubDevEnumTimings(node, pad);
if (!ret)
has_timings = true;
fail_on_test(ret && ret != ENOTTY);
}
return has_timings ? 0 : ENOTTY;
}
for (i = 0; i < node->inputs; i++) {
struct v4l2_input input;
input.index = i;
ret = doioctl(node, VIDIOC_ENUMINPUT, &input);
if (ret)
return fail("could not enumerate input %d?!\n", i);
ret = doioctl(node, VIDIOC_S_INPUT, &input.index);
if (ret)
return fail("could not select input %d.\n", i);
if (input.capabilities & V4L2_IN_CAP_DV_TIMINGS)
has_timings = true;
if (checkTimings(node, input.capabilities & V4L2_IN_CAP_DV_TIMINGS, true))
return fail("Timings check failed for input %d.\n", i);
}
for (o = 0; o < node->outputs; o++) {
struct v4l2_output output;
output.index = o;
ret = doioctl(node, VIDIOC_ENUMOUTPUT, &output);
if (ret)
return fail("could not enumerate output %d?!\n", o);
ret = doioctl(node, VIDIOC_S_OUTPUT, &output.index);
if (ret)
return fail("could not select output %d.\n", o);
if (output.capabilities & V4L2_OUT_CAP_DV_TIMINGS)
has_timings = true;
if (checkTimings(node, output.capabilities & V4L2_OUT_CAP_DV_TIMINGS, false))
return fail("Timings check failed for output %d.\n", o);
}
return has_timings ? 0 : ENOTTY;
}
static int checkTimingsCap(struct node *node, unsigned pad, bool has_timings)
{
struct v4l2_dv_timings_cap timingscap;
int ret;
memset(&timingscap, 0xff, sizeof(timingscap));
timingscap.pad = pad;
ret = doioctl(node, VIDIOC_DV_TIMINGS_CAP, &timingscap);
if (node->is_subdev() && ret == ENOTTY)
return ENOTTY;
if (ret && has_timings)
return fail("TIMINGS cap set, but could not get timings caps\n");
if (!ret && !has_timings)
return fail("TIMINGS cap not set, but could still get timings caps\n");
if (ret && !has_timings)
return 0;
if (check_0(timingscap.reserved, sizeof(timingscap.reserved)))
return fail("reserved not zeroed\n");
fail_on_test(timingscap.pad != pad);
fail_on_test(timingscap.type != V4L2_DV_BT_656_1120);
if (check_0(timingscap.bt.reserved, sizeof(timingscap.bt.reserved)))
return fail("reserved not zeroed\n");
fail_on_test(timingscap.bt.min_width > timingscap.bt.max_width);
fail_on_test(timingscap.bt.min_height > timingscap.bt.max_height);
fail_on_test(timingscap.bt.min_pixelclock > timingscap.bt.max_pixelclock);
if (!node->is_subdev())
return 0;
memset(&timingscap, 0, sizeof(timingscap));
timingscap.pad = node->is_subdev() ? node->entity.pads : 1;
fail_on_test(doioctl(node, VIDIOC_DV_TIMINGS_CAP, &timingscap) != EINVAL);
return 0;
}
int testTimingsCap(struct node *node)
{
int ret;
unsigned i, o;
bool has_timings = false;
if (node->is_subdev()) {
for (unsigned pad = 0; pad < node->entity.pads; pad++) {
bool is_input = node->pads[pad].flags & MEDIA_PAD_FL_SINK;
ret = checkTimingsCap(node, pad, true);
if (ret && ret != ENOTTY)
return fail("EDID check failed for %s pad %u.\n",
is_input ? "sink" : "source", pad);
if (!ret)
has_timings = true;
}
return has_timings ? 0 : ENOTTY;
}
for (i = 0; i < node->inputs; i++) {
struct v4l2_input input;
input.index = i;
ret = doioctl(node, VIDIOC_ENUMINPUT, &input);
if (ret)
return fail("could not enumerate input %d?!\n", i);
ret = doioctl(node, VIDIOC_S_INPUT, &input.index);
if (ret)
return fail("could not select input %d.\n", i);
if (input.capabilities & V4L2_IN_CAP_DV_TIMINGS)
has_timings = true;
if (checkTimingsCap(node, 0, input.capabilities & V4L2_IN_CAP_DV_TIMINGS))
return fail("Timings cap failed for input %d.\n", i);
}
for (o = 0; o < node->outputs; o++) {
struct v4l2_output output;
output.index = o;
ret = doioctl(node, VIDIOC_ENUMOUTPUT, &output);
if (ret)
return fail("could not enumerate output %d?!\n", o);
ret = doioctl(node, VIDIOC_S_OUTPUT, &output.index);
if (ret)
return fail("could not select output %d.\n", o);
if (output.capabilities & V4L2_OUT_CAP_DV_TIMINGS)
has_timings = true;
if (checkTimingsCap(node, 0, output.capabilities & V4L2_OUT_CAP_DV_TIMINGS))
return fail("Timings cap check failed for output %d.\n", o);
}
return has_timings ? 0 : ENOTTY;
}
static int checkEdid(struct node *node, unsigned pad, bool is_input)
{
struct v4l2_edid edid;
__u8 data[256 * 128] = { 0 };
unsigned blocks;
bool has_edid;
int ret;
memset(edid.reserved, 0xff, sizeof(edid.reserved));
edid.pad = pad;
edid.start_block = 0;
edid.blocks = 0;
edid.edid = (__u8 *)0x0eadbeef;
ret = doioctl(node, VIDIOC_G_EDID, &edid);
if (ret == ENOTTY) {
memset(&edid, 0, sizeof(edid));
edid.pad = pad;
fail_on_test(doioctl(node, VIDIOC_S_EDID, &edid) != ENOTTY);
return ENOTTY;
}
has_edid = ret == 0;
fail_on_test(ret && ret != EINVAL);
fail_on_test(!ret && check_0(edid.reserved, sizeof(edid.reserved)));
fail_on_test(edid.start_block);
fail_on_test(edid.blocks > 256);
fail_on_test(!has_edid && edid.blocks);
fail_on_test(edid.pad != pad);
blocks = edid.blocks;
edid.edid = data;
ret = doioctl(node, VIDIOC_G_EDID, &edid);
if (!has_edid)
fail_on_test(ret != EINVAL);
else
fail_on_test(ret != 0 && ret != ENODATA);
fail_on_test(edid.blocks != blocks);
if (has_edid) {
edid.start_block = edid.blocks ? edid.blocks : 1;
ret = doioctl(node, VIDIOC_G_EDID, &edid);
fail_on_test(ret != EINVAL && ret != ENODATA);
if (blocks > 1) {
edid.start_block = 1;
edid.blocks = blocks;
fail_on_test(doioctl(node, VIDIOC_G_EDID, &edid));
fail_on_test(edid.blocks != blocks - 1);
}
edid.start_block = 0;
edid.blocks = 256;
ret = doioctl(node, VIDIOC_G_EDID, &edid);
fail_on_test(ret && ret != ENODATA);
if (!ret)
fail_on_test(edid.blocks != blocks);
edid.pad = ~0;
fail_on_test(doioctl(node, VIDIOC_G_EDID, &edid) != EINVAL);
edid.pad = pad;
}
memset(edid.reserved, 0xff, sizeof(edid.reserved));
edid.blocks = blocks;
ret = doioctl(node, VIDIOC_S_EDID, &edid);
if (ret == ENOTTY) {
fail_on_test(is_input);
return 0;
}
fail_on_test(!is_input);
if (!has_edid) {
fail_on_test(ret != EINVAL);
return 0;
}
fail_on_test(ret);
fail_on_test(check_0(edid.reserved, sizeof(edid.reserved)));
edid.blocks = blocks;
edid.pad = ~0;
ret = doioctl(node, VIDIOC_S_EDID, &edid);
fail_on_test(ret != EINVAL);
if (blocks < 256) {
edid.blocks = 256;
edid.pad = pad;
ret = doioctl(node, VIDIOC_S_EDID, &edid);
fail_on_test(ret != E2BIG);
fail_on_test(edid.blocks == 0 || edid.blocks >= 256);
fail_on_test(edid.pad != pad);
}
if (!has_mmu)
return 0;
edid.blocks = 1;
edid.pad = pad;
edid.edid = nullptr;
ret = doioctl(node, VIDIOC_S_EDID, &edid);
fail_on_test(ret != EFAULT);
edid.edid = (__u8 *)0x0eadbeef;
ret = doioctl(node, VIDIOC_S_EDID, &edid);
fail_on_test(ret != EFAULT);
return 0;
}
int testEdid(struct node *node)
{
bool has_edid = false;
unsigned i, o;
int ret;
if (node->is_subdev()) {
for (unsigned pad = 0; pad < node->entity.pads; pad++) {
bool is_input = node->pads[pad].flags & MEDIA_PAD_FL_SOURCE;
ret = checkEdid(node, pad, is_input);
if (ret && ret != ENOTTY)
return fail("EDID check failed for %s pad %u.\n",
is_input ? "source" : "sink", pad);
if (!ret)
has_edid = true;
}
return has_edid ? 0 : ENOTTY;
}
for (i = 0; i < node->inputs; i++) {
struct v4l2_input input;
input.index = i;
ret = doioctl(node, VIDIOC_ENUMINPUT, &input);
if (ret)
return fail("could not enumerate input %d?!\n", i);
ret = doioctl(node, VIDIOC_S_INPUT, &input.index);
if (ret)
return fail("could not select input %d.\n", i);
ret = checkEdid(node, i, true);
if (ret && ret != ENOTTY)
return fail("EDID check failed for input %d.\n", i);
if (input.capabilities & V4L2_IN_CAP_DV_TIMINGS)
has_edid = !ret ? true : has_edid;
}
for (o = 0; o < node->outputs; o++) {
struct v4l2_output output;
output.index = o;
ret = doioctl(node, VIDIOC_ENUMOUTPUT, &output);
if (ret)
return fail("could not enumerate output %d?!\n", o);
ret = doioctl(node, VIDIOC_S_OUTPUT, &output.index);
if (ret)
return fail("could not select output %d.\n", o);
ret = checkEdid(node, o, false);
if (ret && ret != ENOTTY)
return fail("EDID check failed for output %d.\n", o);
if (output.capabilities & V4L2_OUT_CAP_DV_TIMINGS)
has_edid = !ret ? true : has_edid;
}
return has_edid ? 0 : ENOTTY;
}