/*
   forward-v4l2-timings.c - v4l2 timings for SoftLab-NSK Forward video boards

   Copyright (C) 2017 - 2024 SoftLab-NSK <forward@softlab.tv>

   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, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "forward.h"
#include "forward-v4l2.h"
#include "forward-v4l2-timings.h"
#include "forward-v4l2-ioctl.h"

#include <linux/v4l2-dv-timings.h>

#include <linux/gcd.h>

const struct forward_v4l2_timings forward_v4l2_timings[] = {
	// 1080i50
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_HD_SDI,
		   false,
		   true,
		   2640,
		   1125,
		   1920,
		   { 540, 540 },
		   { 19, 582 },
		   { 559, 1122 },
		   { 1124, 562 },
		   { 20, 20 },
		   { 3, 2 },
		   0 } },
	// 1080i60
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_HD_SDI,
		   false,
		   true,
		   2200,
		   1125,
		   1920,
		   { 540, 540 },
		   { 19, 582 },
		   { 559, 1122 },
		   { 1124, 562 },
		   { 20, 20 },
		   { 3, 2 },
		   0 } },
	// 1080i59.94
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_HD_SDI,
		   true,
		   true,
		   2200,
		   1125,
		   1920,
		   { 540, 540 },
		   { 19, 582 },
		   { 559, 1122 },
		   { 1124, 562 },
		   { 20, 20 },
		   { 3, 2 },
		   0 } },
	// 576i50
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_SD_SDI,
		   false,
		   true,
		   864,
		   625,
		   720,
		   { 288, 288 },
		   { 21, 334 },
		   { 309, 622 },
		   { 624, 311 },
		   { 22, 23 },
		   { 2, 2 },
		   0 } },
	// 480i59.94
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_SD_SDI,
		   false,
		   true,
		   858,
		   525,
		   720,
		   { 244, 243 },
		   { 15, 278 },
		   { 259, 521 },
		   { 524, 261 },
		   { 19, 19 },
		   { 0, 0 },
		   0 } },
	// 720p50
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_HD_SDI,
		   false,
		   false,
		   1980,
		   750,
		   1280,
		   { 720, 720 },
		   { 24, 24 },
		   { 744, 744 },
		   { 749, 749 },
		   { 25, 25 },
		   { 5, 5 },
		   0 } },
	// 720p60
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_HD_SDI,
		   false,
		   false,
		   1650,
		   750,
		   1280,
		   { 720, 720 },
		   { 24, 24 },
		   { 744, 744 },
		   { 749, 749 },
		   { 25, 25 },
		   { 5, 5 },
		   0 } },
	// 720p59.94
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_HD_SDI,
		   true,
		   false,
		   1650,
		   750,
		   1280,
		   { 720, 720 },
		   { 24, 24 },
		   { 744, 744 },
		   { 749, 749 },
		   { 25, 25 },
		   { 5, 5 },
		   0 } },
	// 1080p25
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_HD_SDI,
		   false,
		   false,
		   2640,
		   1125,
		   1920,
		   { 1080, 1080 },
		   { 40, 40 },
		   { 1120, 1120 },
		   { 1124, 1124 },
		   { 41, 41 },
		   { 4, 4 },
		   0 } },
	// 1080p30
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_HD_SDI,
		   false,
		   false,
		   2200,
		   1125,
		   1920,
		   { 1080, 1080 },
		   { 40, 40 },
		   { 1120, 1120 },
		   { 1124, 1124 },
		   { 41, 41 },
		   { 4, 4 },
		   0 } },
	// 1080p29.97
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_HD_SDI,
		   true,
		   false,
		   2200,
		   1125,
		   1920,
		   { 1080, 1080 },
		   { 40, 40 },
		   { 1120, 1120 },
		   { 1124, 1124 },
		   { 41, 41 },
		   { 4, 4 },
		   0 } },
	// 1080p24
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_HD_SDI,
		   false,
		   false,
		   2750,
		   1125,
		   1920,
		   { 1080, 1080 },
		   { 40, 40 },
		   { 1120, 1120 },
		   { 1124, 1124 },
		   { 41, 41 },
		   { 4, 4 },
		   0 } },
	// 1080p23.98
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_HD_SDI,
		   true,
		   false,
		   2750,
		   1125,
		   1920,
		   { 1080, 1080 },
		   { 40, 40 },
		   { 1120, 1120 },
		   { 1124, 1124 },
		   { 41, 41 },
		   { 4, 4 },
		   0 } },
	// 1080p50
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_3G_SDI,
		   false,
		   false,
		   2640,
		   1125,
		   1920,
		   { 1080, 1080 },
		   { 40, 40 },
		   { 1120, 1120 },
		   { 1124, 1124 },
		   { 41, 41 },
		   { 4, 4 },
		   0 } },
	// 1080p60
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_3G_SDI,
		   false,
		   false,
		   2200,
		   1125,
		   1920,
		   { 1080, 1080 },
		   { 40, 40 },
		   { 1120, 1120 },
		   { 1124, 1124 },
		   { 41, 41 },
		   { 4, 4 },
		   0 } },
	// 1080p59.94
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_3G_SDI,
		   true,
		   false,
		   2200,
		   1125,
		   1920,
		   { 1080, 1080 },
		   { 40, 40 },
		   { 1120, 1120 },
		   { 1124, 1124 },
		   { 41, 41 },
		   { 4, 4 },
		   0 } },
	// 2160p25
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_DUAL_3G_SDI,
		   false,
		   false,
		   5280,
		   2250,
		   3840,
		   { 2160, 2160 },
		   { 81, 81 },
		   { 2241, 2241 },
		   { 2249, 2249 },
		   { 82, 82 },
		   { 8, 8 },
		   0 } },
	// 2160p30
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_DUAL_3G_SDI,
		   false,
		   false,
		   4400,
		   2250,
		   3840,
		   { 2160, 2160 },
		   { 81, 81 },
		   { 2241, 2241 },
		   { 2249, 2249 },
		   { 82, 82 },
		   { 8, 8 },
		   0 } },
	// 2160p29.97
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_DUAL_3G_SDI,
		   true,
		   false,
		   4400,
		   2250,
		   3840,
		   { 2160, 2160 },
		   { 81, 81 },
		   { 2241, 2241 },
		   { 2249, 2249 },
		   { 82, 82 },
		   { 8, 8 },
		   0 } },
	// 2160p50
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_QUAD_3G_SDI,
		   false,
		   false,
		   5280,
		   2250,
		   3840,
		   { 2160, 2160 },
		   { 81, 81 },
		   { 2241, 2241 },
		   { 2249, 2249 },
		   { 82, 82 },
		   { 8, 8 },
		   0 } },
	// 2160p60
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_QUAD_3G_SDI,
		   false,
		   false,
		   4400,
		   2250,
		   3840,
		   { 2160, 2160 },
		   { 81, 81 },
		   { 2241, 2241 },
		   { 2249, 2249 },
		   { 82, 82 },
		   { 8, 8 },
		   0 } },
	// 2160p59.94
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_QUAD_3G_SDI,
		   true,
		   false,
		   4400,
		   2250,
		   3840,
		   { 2160, 2160 },
		   { 81, 81 },
		   { 2241, 2241 },
		   { 2249, 2249 },
		   { 82, 82 },
		   { 8, 8 },
		   0 } },
	// 2160p25
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_6G_SDI,
		   false,
		   false,
		   5280,
		   2250,
		   3840,
		   { 2160, 2160 },
		   { 81, 81 },
		   { 2241, 2241 },
		   { 2249, 2249 },
		   { 82, 82 },
		   { 8, 8 },
		   0 } },
	// 2160p30
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_6G_SDI,
		   false,
		   false,
		   4400,
		   2250,
		   3840,
		   { 2160, 2160 },
		   { 81, 81 },
		   { 2241, 2241 },
		   { 2249, 2249 },
		   { 82, 82 },
		   { 8, 8 },
		   0 } },
	// 2160p29.97
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_6G_SDI,
		   true,
		   false,
		   4400,
		   2250,
		   3840,
		   { 2160, 2160 },
		   { 81, 81 },
		   { 2241, 2241 },
		   { 2249, 2249 },
		   { 82, 82 },
		   { 8, 8 },
		   0 } },
	// 2160p50
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_12G_SDI,
		   false,
		   false,
		   5280,
		   2250,
		   3840,
		   { 2160, 2160 },
		   { 81, 81 },
		   { 2241, 2241 },
		   { 2249, 2249 },
		   { 82, 82 },
		   { 8, 8 },
		   0 } },
	// 2160p60
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_12G_SDI,
		   false,
		   false,
		   4400,
		   2250,
		   3840,
		   { 2160, 2160 },
		   { 81, 81 },
		   { 2241, 2241 },
		   { 2249, 2249 },
		   { 82, 82 },
		   { 8, 8 },
		   0 } },
	// 2160p59.94
	{ .type = FORWARD_TIMING_SDI,
	  .sdi = { FORWARD_SDI_MODE_12G_SDI,
		   true,
		   false,
		   4400,
		   2250,
		   3840,
		   { 2160, 2160 },
		   { 81, 81 },
		   { 2241, 2241 },
		   { 2249, 2249 },
		   { 82, 82 },
		   { 8, 8 },
		   0 } },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_1920X1080I50 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_1920X1080I60 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_720X576I50 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_720X576P50 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_720X480I59_94 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_720X480P59_94 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_1280X720P50 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_1280X720P60 },
	// 720p100
	{ .type = FORWARD_TIMING_HDMI,
	  .hdmi = { .type = V4L2_DV_BT_656_1120,
		    V4L2_INIT_BT_TIMINGS(1280, 720, 0,
					 V4L2_DV_HSYNC_POS_POL | V4L2_DV_VSYNC_POS_POL, 148500000,
					 440, 40, 220, 5, 5, 20, 0, 0, 0, V4L2_DV_BT_STD_CEA861,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
					 V4L2_DV_FL_IS_CE_VIDEO
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
						 | V4L2_DV_FL_HAS_CEA861_VIC,
					 { 0, 0 }, 41
#endif
					 ) } },
	// 720p120
	{ .type = FORWARD_TIMING_HDMI,
	  .hdmi = { .type = V4L2_DV_BT_656_1120,
		    V4L2_INIT_BT_TIMINGS(1280, 720, 0,
					 V4L2_DV_HSYNC_POS_POL | V4L2_DV_VSYNC_POS_POL, 148500000,
					 110, 40, 220, 5, 5, 20, 0, 0, 0, V4L2_DV_BT_STD_CEA861,
					 V4L2_DV_FL_CAN_REDUCE_FPS
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
						 | V4L2_DV_FL_IS_CE_VIDEO
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
						 | V4L2_DV_FL_HAS_CEA861_VIC,
					 { 0, 0 }, 47
#endif
					 ) } },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_1280X720P25 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_1280X720P30 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_1280X720P24 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_1920X1080P25 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_1920X1080P30 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_1920X1080P24 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_1920X1080P50 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_1920X1080P60 },
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
	// 1080p100
	{ .type = FORWARD_TIMING_HDMI,
	  .hdmi = { .type = V4L2_DV_BT_656_1120,
		    V4L2_INIT_BT_TIMINGS(
			    1920, 1080, 0, V4L2_DV_HSYNC_POS_POL | V4L2_DV_VSYNC_POS_POL, 297000000,
			    528, 44, 148, 4, 5, 36, 0, 0, 0, V4L2_DV_BT_STD_CEA861,
			    V4L2_DV_FL_IS_CE_VIDEO | V4L2_DV_FL_HAS_CEA861_VIC, { 0, 0 }, 64) } },
	// 1080p120
	{ .type = FORWARD_TIMING_HDMI,
	  .hdmi = { .type = V4L2_DV_BT_656_1120,
		    V4L2_INIT_BT_TIMINGS(1920, 1080, 0,
					 V4L2_DV_HSYNC_POS_POL | V4L2_DV_VSYNC_POS_POL, 297000000,
					 88, 44, 148, 4, 5, 36, 0, 0, 0,
					 V4L2_DV_BT_STD_DMT | V4L2_DV_BT_STD_CEA861,
					 V4L2_DV_FL_CAN_REDUCE_FPS | V4L2_DV_FL_IS_CE_VIDEO |
						 V4L2_DV_FL_HAS_CEA861_VIC,
					 { 0, 0 }, 63

					 ) } },
	// 1080p200
	{ .type = FORWARD_TIMING_HDMI,
	  .hdmi = { .type = V4L2_DV_BT_656_1120,
		    V4L2_INIT_BT_TIMINGS(1920, 1080, 0,
					 V4L2_DV_HSYNC_POS_POL | V4L2_DV_VSYNC_POS_POL, 594000000,
					 528, 44, 148, 4, 5, 36, 0, 0, 0, V4L2_DV_BT_STD_CEA861, 0,
					 { 0, 0 }, 0) } },
	// 1080p240
	{ .type = FORWARD_TIMING_HDMI,
	  .hdmi = { .type = V4L2_DV_BT_656_1120,
		    V4L2_INIT_BT_TIMINGS(1920, 1080, 0,
					 V4L2_DV_HSYNC_POS_POL | V4L2_DV_VSYNC_POS_POL, 594000000,
					 88, 44, 148, 4, 5, 36, 0, 0, 0,
					 V4L2_DV_BT_STD_DMT | V4L2_DV_BT_STD_CEA861,
					 V4L2_DV_FL_CAN_REDUCE_FPS, { 0, 0 }, 0

					 ) } },
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_3840X2160P25 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_3840X2160P30 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_3840X2160P24 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_3840X2160P50 },
	{ .type = FORWARD_TIMING_HDMI, .hdmi = V4L2_DV_BT_CEA_3840X2160P60 },
#else
	{ .type = FORWARD_TIMING_HDMI,
	  .hdmi = { .type = V4L2_DV_BT_656_1120,
		    V4L2_INIT_BT_TIMINGS(
			    3840, 2160, 0, V4L2_DV_HSYNC_POS_POL | V4L2_DV_VSYNC_POS_POL, 297000000,
			    1056, 88, 296, 8, 10, 72, 0, 0, 0, V4L2_DV_BT_STD_CEA861, 0) } },
	{ .type = FORWARD_TIMING_HDMI,
	  .hdmi = { .type = V4L2_DV_BT_656_1120,
		    V4L2_INIT_BT_TIMINGS(3840, 2160, 0,
					 V4L2_DV_HSYNC_POS_POL | V4L2_DV_VSYNC_POS_POL, 297000000,
					 176, 88, 296, 8, 10, 72, 0, 0, 0, V4L2_DV_BT_STD_CEA861,
					 V4L2_DV_FL_CAN_REDUCE_FPS) } },
	{ .type = FORWARD_TIMING_HDMI,
	  .hdmi = { .type = V4L2_DV_BT_656_1120,
		    V4L2_INIT_BT_TIMINGS(3840, 2160, 0,
					 V4L2_DV_HSYNC_POS_POL | V4L2_DV_VSYNC_POS_POL, 297000000,
					 1276, 88, 296, 8, 10, 72, 0, 0, 0, V4L2_DV_BT_STD_CEA861,
					 V4L2_DV_FL_CAN_REDUCE_FPS) } },
	{ .type = FORWARD_TIMING_HDMI,
	  .hdmi = { .type = V4L2_DV_BT_656_1120,
		    V4L2_INIT_BT_TIMINGS(
			    3840, 2160, 0, V4L2_DV_HSYNC_POS_POL | V4L2_DV_VSYNC_POS_POL, 594000000,
			    1056, 88, 296, 8, 10, 72, 0, 0, 0, V4L2_DV_BT_STD_CEA861, 0) } },
	{ .type = FORWARD_TIMING_HDMI,
	  .hdmi = { .type = V4L2_DV_BT_656_1120,
		    V4L2_INIT_BT_TIMINGS(3840, 2160, 0,
					 V4L2_DV_HSYNC_POS_POL | V4L2_DV_VSYNC_POS_POL, 594000000,
					 176, 88, 296, 8, 10, 72, 0, 0, 0, V4L2_DV_BT_STD_CEA861,
					 V4L2_DV_FL_CAN_REDUCE_FPS) } },
#endif
};

const int forward_v4l2_timings_count =
	sizeof(forward_v4l2_timings) / sizeof(struct forward_v4l2_timings);

unsigned int forward_v4l2_total_pixels(const struct forward_v4l2_timings *t)
{
	if (t->type == FORWARD_TIMING_HDMI)
		return V4L2_DV_BT_FRAME_HEIGHT(&t->hdmi.bt) * V4L2_DV_BT_FRAME_WIDTH(&t->hdmi.bt);
	else if (t->type == FORWARD_TIMING_SDI)
		return t->sdi.width * t->sdi.height;

	return 0;
}

unsigned int forward_v4l2_total_height(const struct forward_v4l2_timings *t)
{
	if (t->type == FORWARD_TIMING_HDMI)
		return V4L2_DV_BT_FRAME_HEIGHT(&t->hdmi.bt);
	else if (t->type == FORWARD_TIMING_SDI)
		return t->sdi.height;

	return 0;
}

unsigned int forward_v4l2_total_width(const struct forward_v4l2_timings *t)
{
	if (t->type == FORWARD_TIMING_HDMI)
		return V4L2_DV_BT_FRAME_WIDTH(&t->hdmi.bt);
	else if (t->type == FORWARD_TIMING_SDI)
		return t->sdi.width;

	return 0;
}

unsigned int forward_v4l2_pixel_clock(const struct forward_v4l2_timings *t)
{
	if (t->type == FORWARD_TIMING_HDMI)
		return t->hdmi.bt.pixelclock;
	else if (t->type == FORWARD_TIMING_SDI) {
		switch (t->sdi.mode) {
		case FORWARD_SDI_MODE_SD_SDI:
			return 13500000;
		case FORWARD_SDI_MODE_HD_SDI:
			return 74250000;
		case FORWARD_SDI_MODE_3G_SDI:
			return 148500000;
		case FORWARD_SDI_MODE_6G_SDI:
		case FORWARD_SDI_MODE_DUAL_3G_SDI:
			return 297000000;
		case FORWARD_SDI_MODE_12G_SDI:
		case FORWARD_SDI_MODE_DUAL_6G_SDI:
		case FORWARD_SDI_MODE_QUAD_3G_SDI:
			return 594000000;
		default:
			return 0;
		}
	}
	return 0;
}

bool forward_v4l2_interlaced(const struct forward_v4l2_timings *t)
{
	return (t->type == FORWARD_TIMING_HDMI) ? t->hdmi.bt.interlaced : t->sdi.interlaced;
}

unsigned int forward_v4l2_visible_height(const struct forward_v4l2_timings *t)
{
	unsigned int result = 0;
	if (t->type == FORWARD_TIMING_SDI) {
		result = t->sdi.active_height[0];
		if (t->sdi.interlaced)
			result += t->sdi.active_height[1];

		if ((t->sdi.mode == FORWARD_SDI_MODE_SD_SDI) && (t->sdi.height == 525))
			result = 480;
	} else if (t->type == FORWARD_TIMING_HDMI)
		result = t->hdmi.bt.height;

	return result;
}

unsigned int forward_v4l2_visible_width(const struct forward_v4l2_timings *t)
{
	return (t->type == FORWARD_TIMING_HDMI) ? t->hdmi.bt.width : t->sdi.active_width;
}

struct v4l2_fract forward_v4l2_frameinterval(const struct forward_v4l2_timings *t)
{
	struct v4l2_fract interval;
	unsigned long fivgcd;

	interval.numerator = forward_v4l2_total_pixels(t);
	interval.denominator = forward_v4l2_pixel_clock(t);

	fivgcd = gcd(interval.numerator, interval.denominator);
	if (!fivgcd)
		fivgcd = 1;
	interval.numerator /= fivgcd;
	interval.denominator /= fivgcd;

	if (((t->type == FORWARD_TIMING_SDI) && t->sdi.m) ||
	    ((t->type == FORWARD_TIMING_HDMI) && t->hdmi.bt.flags & V4L2_DV_FL_REDUCED_FPS)) {
		interval.numerator *= 1001;
		interval.denominator *= 1000;

		fivgcd = gcd(interval.numerator, interval.denominator);
		if (!fivgcd)
			fivgcd = 1;
		interval.numerator /= fivgcd;
		interval.denominator /= fivgcd;
	}
	return interval;
}

struct v4l2_fract forward_v4l2_fps(const struct forward_v4l2_timings *t)
{
	struct v4l2_fract fps, interval = forward_v4l2_frameinterval(t);
	fps.denominator = interval.numerator;
	fps.numerator = interval.denominator;
	return fps;
}

void forward_v4l2_fill_pix_fmt(const struct forward_v4l2_timings *t, struct v4l2_pix_format *f)
{
	bool interlaced = forward_v4l2_interlaced(t);
	bool asi = (f->pixelformat == V4L2_PIX_FMT_MPEG) ||
		   (f->pixelformat == V4L2_PIX_FMT_SL_RAW_ASI);
	bool raw = (f->pixelformat == V4L2_PIX_FMT_SL_RAW) ||
		   (f->pixelformat == V4L2_PIX_FMT_SL_RAW_ASI);

	if (!raw) {
		f->width = forward_v4l2_visible_width(t);
		f->height = forward_v4l2_visible_height(t);
	} else {
		f->width = forward_v4l2_total_width(t);
		f->height = forward_v4l2_total_height(t);
	}

	if (raw && interlaced) {
		f->field = V4L2_FIELD_ALTERNATE;
	} else if (interlaced) {
		if ((f->field != V4L2_FIELD_ALTERNATE) && (f->field != V4L2_FIELD_INTERLACED) &&
		    (f->field != V4L2_FIELD_INTERLACED_TB) &&
		    (f->field != V4L2_FIELD_INTERLACED_BT))
			f->field = V4L2_FIELD_INTERLACED;
	} else
		f->field = V4L2_FIELD_NONE;

	if (asi)
		f->field = V4L2_FIELD_NONE;

	if (f->field == V4L2_FIELD_ALTERNATE)
		f->height = (f->height - 1) / 2 + 1;

	switch (f->pixelformat) {
	case V4L2_PIX_FMT_SL_RAW_ASI:
	case V4L2_PIX_FMT_SL_RAW:
	case V4L2_PIX_FMT_MPEG:
		f->bytesperline = 0;
		break;
	case V4L2_PIX_FMT_YUYV:
	case V4L2_PIX_FMT_UYVY:
		f->bytesperline = f->width * 2;
		break;
	case V4L2_PIX_FMT_BGR24:
		f->bytesperline = f->width * 3;
		break;
	case V4L2_PIX_FMT_YUV420:
		f->bytesperline = f->width * 3 / 2;
		break;
	case V4L2_PIX_FMT_V210:
		f->bytesperline = f->width * 8 / 3;
		f->bytesperline = ((f->bytesperline - 1) / 128 + 1) * 128;
		break;
	/*case V4L2_PIX_FMT_Y210:
		f->bytesperline = f->width * 4;
		break;*/
	default:
		f->pixelformat = V4L2_PIX_FMT_UYVY;
		f->bytesperline = f->width * 2;
		break;
	}

	if (f->pixelformat == V4L2_PIX_FMT_SL_RAW)
		f->sizeimage = f->width * f->height * 20 / 8;
	else if (f->pixelformat == V4L2_PIX_FMT_SL_RAW_ASI)
		f->sizeimage = FORWARD_V4L2_ASI_DEFAULT_BUFFER_SIZE;
	else if (f->pixelformat == V4L2_PIX_FMT_MPEG)
		f->sizeimage = FORWARD_V4L2_ASI_DEFAULT_BUFFER_SIZE;
	else
		f->sizeimage = f->bytesperline * f->height;
	f->colorspace = V4L2_COLORSPACE_REC709;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0)
	f->flags = 0;
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
	f->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
	f->quantization = V4L2_QUANTIZATION_DEFAULT;
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
	f->xfer_func = V4L2_XFER_FUNC_DEFAULT;
#endif
}

void forward_v4l2_fill_vbi_fmt(const struct forward_v4l2_timings *t, struct v4l2_vbi_format *fmt)
{
	bool interlaced = forward_v4l2_interlaced(t);

	if (t->type != FORWARD_TIMING_SDI)
		return;

	fmt->count[0] = t->sdi.topVBIs[0];
	fmt->count[1] = interlaced ? t->sdi.topVBIs[1] : 0;
	fmt->start[0] = 1;
	fmt->start[1] = interlaced ? (t->sdi.field_switch[1] + 2) : 1;
	if ((t->sdi.mode == FORWARD_SDI_MODE_SD_SDI) && (t->sdi.height == 525))
		fmt->start[1] += 1;
	fmt->offset = (t->sdi.width - t->sdi.active_width) * 2;
	fmt->sample_format = V4L2_PIX_FMT_Y10;
	switch (t->sdi.mode) {
	case FORWARD_SDI_MODE_SD_SDI:
		fmt->sampling_rate = 27000000;
		break;
	case FORWARD_SDI_MODE_HD_SDI:
		fmt->sampling_rate = 148500000;
		break;
	case FORWARD_SDI_MODE_3G_SDI:
		fmt->sampling_rate = 297000000;
		break;
	case FORWARD_SDI_MODE_DUAL_3G_SDI:
	case FORWARD_SDI_MODE_6G_SDI:
		fmt->sampling_rate = 594000000;
		break;
	case FORWARD_SDI_MODE_DUAL_6G_SDI:
	case FORWARD_SDI_MODE_QUAD_3G_SDI:
	case FORWARD_SDI_MODE_12G_SDI:
		fmt->sampling_rate = 1188000000;
		break;
	default:
		fmt->sampling_rate = 0;
		break;
	}
	fmt->samples_per_line = t->sdi.active_width * 2;
}

void forward_v4l2_fill_dv_timings(const struct forward_v4l2_timings *t, struct v4l2_dv_timings *dv)
{
	bool canM;
	struct v4l2_fract interval;

	if (t->type == FORWARD_TIMING_HDMI) {
		*dv = t->hdmi;
		return;
	}
	interval = forward_v4l2_frameinterval(t);
	canM = (((interval.denominator / interval.numerator) % 6) == 0) || t->sdi.m;

	memset(dv, 0, sizeof(struct v4l2_dv_timings));

	dv->type = V4L2_DV_BT_656_1120;
	dv->bt.width = forward_v4l2_visible_width(t);
	dv->bt.height = forward_v4l2_visible_height(t);
	dv->bt.interlaced = t->sdi.interlaced ? V4L2_DV_INTERLACED : V4L2_DV_PROGRESSIVE;
	dv->bt.polarities = V4L2_DV_VSYNC_POS_POL | V4L2_DV_HSYNC_POS_POL;
	dv->bt.pixelclock = forward_v4l2_pixel_clock(t);
	dv->bt.hfrontporch = 0;
	dv->bt.hsync = t->sdi.width - t->sdi.active_width;
	dv->bt.hbackporch = 0;
	dv->bt.vfrontporch = 0;
	dv->bt.vsync = t->sdi.topVBIs[0] + t->sdi.bottomVBIs[1];
	dv->bt.vbackporch = 0;
	dv->bt.il_vfrontporch = 0;
	dv->bt.il_vsync = t->sdi.interlaced ? (t->sdi.topVBIs[1] + t->sdi.bottomVBIs[0]) : 0;
	dv->bt.il_vbackporch = 0;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0)
	dv->bt.standards = V4L2_DV_BT_STD_SDI;
#else
	dv->bt.standards = V4L2_DV_BT_STD_CEA861;
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
	dv->bt.flags = (canM ? V4L2_DV_FL_CAN_REDUCE_FPS : 0) |
		       (t->sdi.m ? V4L2_DV_FL_REDUCED_FPS : 0) | V4L2_DV_FL_IS_CE_VIDEO;
#else
	dv->bt.flags = (canM ? V4L2_DV_FL_CAN_REDUCE_FPS : 0) |
		       (t->sdi.m ? V4L2_DV_FL_REDUCED_FPS : 0);
#endif

	if ((t->sdi.mode == FORWARD_SDI_MODE_SD_SDI) && (t->sdi.height == 525)) {
		dv->bt.vsync += 3;
		dv->bt.il_vsync += 4;
	}
}

bool forward_v4l2_dv_match(const struct forward_v4l2_timings *t, const struct v4l2_dv_timings *dv)
{
	u64 pixClk = (u64)forward_v4l2_pixel_clock(t);

	if (dv->bt.pixelclock != pixClk)
		return false;

	if (t->type == FORWARD_TIMING_SDI) {
		bool dvM = dv->bt.flags & V4L2_DV_FL_REDUCED_FPS;

		if ((dv->bt.interlaced && !t->sdi.interlaced) ||
		    (!dv->bt.interlaced && t->sdi.interlaced))
			return false;

		if (((int)V4L2_DV_BT_FRAME_WIDTH(&dv->bt) != t->sdi.width) ||
		    ((int)V4L2_DV_BT_FRAME_HEIGHT(&dv->bt) != t->sdi.height))
			return false;

		if ((dv->bt.width != forward_v4l2_visible_width(t)) ||
		    (dv->bt.height != forward_v4l2_visible_height(t)))
			return false;

		if ((dvM && !t->sdi.m) || (!dvM && t->sdi.m))
			return false;
	} else if (t->type == FORWARD_TIMING_HDMI) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0)
		return v4l2_match_dv_timings(&t->hdmi, dv, t->hdmi.bt.pixelclock / 1000, true);
#else
		return v4l2_match_dv_timings(&t->hdmi, dv, t->hdmi.bt.pixelclock / 1000);
#endif
	}
	return true;
}

bool forward_v4l2_timings_fps_divisible(const struct forward_v4l2_timings *t1,
					const struct forward_v4l2_timings *t2)
{
	struct v4l2_fract fps1 = forward_v4l2_fps(t1), fps2 = forward_v4l2_fps(t2);

	u32 x = fps1.numerator * fps2.denominator;
	u32 y = fps2.numerator * fps1.denominator;

	if (!x || !y)
		return false;

	return (x > y) ? ((x % y) == 0) : ((y % x) == 0);
}

bool forward_v4l2_timing_guess_sdi(enum forward_sdi_mode mode, bool interlaced, int width,
				   int height, int active_width, int active_height, u32 vpid,
				   u64 pixelclk, struct forward_v4l2_timings *timings)
{
	int i;

	for (i = 0; i < forward_v4l2_timings_count; i++) {
		const struct forward_v4l2_timings *cur = &forward_v4l2_timings[i];

		u64 tpixclk = forward_v4l2_pixel_clock(cur);
		u64 clk_ratio;

		if (cur->type != FORWARD_TIMING_SDI)
			continue;

		if (cur->sdi.mode != mode)
			continue;

		if (cur->sdi.interlaced ? !interlaced : interlaced)
			continue;

		if ((cur->sdi.width != width) || (cur->sdi.height != height))
			continue;

		if ((cur->sdi.active_width != active_width) ||
		    (cur->sdi.active_height[0] != active_height)) {
			if (!((cur->sdi.mode == FORWARD_SDI_MODE_SD_SDI) &&
			      (cur->sdi.height == 525) &&
			      ((active_height == 243) || (active_height == 244))))
				continue;
		}

		if (cur->sdi.m)
			tpixclk = tpixclk * 1000 / 1001;

		if (!pixelclk)
			pixelclk = tpixclk;

		clk_ratio = tpixclk * 10000 / pixelclk;

		if ((clk_ratio < 9995) || (clk_ratio > 10005))
			continue;

		*timings = *cur;
		return true;
	}

	return false;
}

bool forward_v4l2_timing_guess_hdmi(bool interlaced, int width, int height, int active_width,
				    int active_height, u8 vic, u64 pixel_clk,
				    struct forward_v4l2_timings *t)
{
	int i;

	for (i = 0; i < forward_v4l2_timings_count; i++) {
		const struct forward_v4l2_timings *cur = &forward_v4l2_timings[i];

		if (cur->type != FORWARD_TIMING_HDMI)
			continue;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
		if (vic) {
			if (cur->hdmi.bt.hdmi_vic == vic) {
				*t = *cur;
				return true;
			}
		}
#endif

		if (pixel_clk < cur->hdmi.bt.pixelclock - cur->hdmi.bt.pixelclock / 500)
			continue;

		if (pixel_clk > cur->hdmi.bt.pixelclock + cur->hdmi.bt.pixelclock / 500)
			continue;

		if ((interlaced && !cur->hdmi.bt.interlaced) ||
		    (!interlaced && cur->hdmi.bt.interlaced))
			continue;

		if (width != (int)V4L2_DV_BT_FRAME_WIDTH(&cur->hdmi.bt))
			continue;

		if (height != (int)V4L2_DV_BT_FRAME_HEIGHT(&cur->hdmi.bt))
			continue;

		if (active_width != cur->hdmi.bt.width)
			continue;

		if (active_height != cur->hdmi.bt.height)
			continue;

		*t = *cur;
		return true;
	}
	return false;
}

bool forward_v4l2_timing_guess_analog(int hsync_freq, int vsync_freq1000,
				      struct forward_v4l2_timings *t)
{
	int i;

	for (i = 0; i < forward_v4l2_timings_count; i++) {
		const struct forward_v4l2_timings *cur = &forward_v4l2_timings[i];
		unsigned int cur_pix_clk, cur_h_clk, cur_v_clk;

		if (cur->type != FORWARD_TIMING_SDI)
			continue;

		cur_pix_clk = forward_v4l2_pixel_clock(cur);
		cur_h_clk = cur_pix_clk / cur->sdi.width;
		if (cur->sdi.m)
			cur_h_clk = cur_h_clk * 1000 / 1001;

		if (!((hsync_freq * 100 < cur_h_clk * 104) && (hsync_freq * 100 > cur_h_clk * 96)))
			continue;

		cur_v_clk = (u64)cur_pix_clk * 1000 / cur->sdi.width / cur->sdi.height;
		if (cur->sdi.interlaced)
			cur_v_clk *= 2;
		if (cur->sdi.m)
			cur_v_clk = cur_v_clk * 1000 / 1001;

		if (!((vsync_freq1000 * 10000 < cur_v_clk * 10005) &&
		      (vsync_freq1000 * 10000 > cur_v_clk * 9995)))
			continue;

		*t = *cur;
		return true;
	}
	return false;
}

bool forward_v4l2_timings_equal(const struct forward_v4l2_timings *x,
				const struct forward_v4l2_timings *y, bool m_match)
{
	if (x->type != y->type)
		return false;

	if (x->type == FORWARD_TIMING_SDI) {
		if ((x->sdi.mode != y->sdi.mode) || (x->sdi.interlaced != y->sdi.interlaced))
			return false;

		if (m_match && (x->sdi.m != y->sdi.m))
			return false;

		if ((x->sdi.width != y->sdi.width) || (x->sdi.height != y->sdi.height))
			return false;

		if ((x->sdi.active_width != y->sdi.active_width) ||
		    (x->sdi.active_height[0] != y->sdi.active_height[0]))
			return false;

		if (x->sdi.interlaced && (x->sdi.active_height[1] != y->sdi.active_height[1]))
			return false;

		if ((x->sdi.active_starts[0] != y->sdi.active_starts[0]) ||
		    (x->sdi.active_stops[0] != y->sdi.active_stops[0]))
			return false;

		if (x->sdi.interlaced && ((x->sdi.active_starts[0] != y->sdi.active_starts[0]) ||
					  (x->sdi.active_stops[0] != y->sdi.active_stops[0])))
			return false;

		if (x->sdi.interlaced && ((x->sdi.field_switch[0] != y->sdi.field_switch[0]) ||
					  (x->sdi.field_switch[1] != y->sdi.field_switch[1])))
			return false;

		if ((x->sdi.topVBIs[0] != y->sdi.topVBIs[0]) ||
		    (x->sdi.bottomVBIs[0] != y->sdi.bottomVBIs[0]))
			return false;

		if (x->sdi.interlaced && ((x->sdi.topVBIs[1] != y->sdi.topVBIs[1]) ||
					  (x->sdi.bottomVBIs[1] != y->sdi.bottomVBIs[1])))
			return false;

		return true;
	} else if (x->type == FORWARD_TIMING_HDMI) {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0)
		return v4l2_match_dv_timings(&x->hdmi, &y->hdmi, x->hdmi.bt.pixelclock / 1000,
					     m_match);
#else
		return v4l2_match_dv_timings(&x->hdmi, &y->hdmi, x->hdmi.bt.pixelclock / 1000);
#endif
	}
	return false;
}

u32 forward_v4l2_timings_vpid(const struct forward_v4l2_timings *t, bool level_b, bool depth10,
			      bool widescreen)
{
	u32 vpid = 0;
	bool reduced = t->sdi.m;
	int rate = 0;
	struct forward_sdi_timings sdi = t->sdi;

	if (sdi.mode == FORWARD_SDI_MODE_SD_SDI) {
		vpid |= (0x81 << 24);
	} else if (sdi.mode == FORWARD_SDI_MODE_HD_SDI) {
		if (sdi.height == 750)
			vpid |= (0x84 << 24);
		else if (sdi.height == 1125)
			vpid |= (0x85 << 24);
	} else if (sdi.mode == FORWARD_SDI_MODE_3G_SDI) {
		if (sdi.height == 750)
			vpid |= (0x88 << 24);
		else if ((sdi.height == 1125) && !level_b)
			vpid |= (0x89 << 24);
		else if ((sdi.height == 1125) && level_b)
			vpid |= (0x8A << 24);
	} else if (sdi.mode == FORWARD_SDI_MODE_DUAL_3G_SDI) {
		vpid |= (0x96 << 24);
	} else if (sdi.mode == FORWARD_SDI_MODE_QUAD_3G_SDI) {
		vpid |= (0x97 << 24);
	} else if (sdi.mode == FORWARD_SDI_MODE_6G_SDI) {
		vpid |= (0xC0 << 24);
	} else if (sdi.mode == FORWARD_SDI_MODE_12G_SDI) {
		vpid |= (0xCE << 24);
	}

	if (!vpid)
		return vpid;

	if (!sdi.interlaced) {
		if (sdi.mode == FORWARD_SDI_MODE_SD_SDI)
			vpid |= 0x40 << 16;
		else
			vpid |= 0xC0 << 16;
	}

	if (sdi.width && sdi.height) {
		if (sdi.mode == FORWARD_SDI_MODE_SD_SDI)
			rate = 13500000 / sdi.width / sdi.height;
		else if (sdi.mode == FORWARD_SDI_MODE_HD_SDI)
			rate = 74250000 / sdi.width / sdi.height;
		else if (sdi.mode == FORWARD_SDI_MODE_3G_SDI)
			rate = 148500000 / sdi.width / sdi.height;
		else if (sdi.mode == FORWARD_SDI_MODE_DUAL_3G_SDI)
			rate = 297000000 / sdi.width / sdi.height;
		else if (sdi.mode == FORWARD_SDI_MODE_QUAD_3G_SDI)
			rate = 594000000 / sdi.width / sdi.height;
		else if (sdi.mode == FORWARD_SDI_MODE_6G_SDI)
			rate = 297000000 / sdi.width / sdi.height;
		else if (sdi.mode == FORWARD_SDI_MODE_12G_SDI)
			rate = 594000000 / sdi.width / sdi.height;
		// FIXME: A bit ugly
		if ((sdi.mode != FORWARD_SDI_MODE_SD_SDI) && reduced)
			rate--;
	}
	switch (rate) {
	case 23:
		vpid |= 0x2 << 16;
		break;
	case 24:
		vpid |= 0x3 << 16;
		break;
	case 25:
		vpid |= 0x5 << 16;
		break;
	case 29:
		vpid |= 0x6 << 16;
		break;
	case 30:
		vpid |= 0x7 << 16;
		break;
	case 50:
		vpid |= 0x9 << 16;
		break;
	case 59:
		vpid |= 0xA << 16;
		break;
	case 60:
		vpid |= 0xB << 16;
		break;
	default:
		break;
	}

	if (sdi.mode == FORWARD_SDI_MODE_SD_SDI)
		vpid |= (widescreen ? 0x80 : 0x00) << 8;

	// TODO: Sampling, channel, range
	vpid |= depth10 ? 0x1 : 0x0;

	return vpid;
}

void forward_v4l2_timings_print(const struct forward_v4l2_timings *t)
{
	int lines;
	int fps_dec = 0, fps_frac = 0;
	bool interlaced;
	struct v4l2_fract fi;

	if ((t->type != FORWARD_TIMING_SDI) && (t->type != FORWARD_TIMING_HDMI)) {
		pr_cont("Unknown");
		return;
	}

	if (t->type == FORWARD_TIMING_SDI) {
		switch (t->sdi.mode) {
		case FORWARD_SDI_MODE_SD_SDI:
			pr_cont("SD-SDI");
			break;
		case FORWARD_SDI_MODE_HD_SDI:
			pr_cont("HD-SDI");
			break;
		case FORWARD_SDI_MODE_3G_SDI:
			pr_cont("3G-SDI");
			break;
		case FORWARD_SDI_MODE_6G_SDI:
			pr_cont("6G-SDI");
			break;
		case FORWARD_SDI_MODE_12G_SDI:
			pr_cont("12G-SDI");
			break;
		case FORWARD_SDI_MODE_DUAL_3G_SDI:
			pr_cont("Dual 3G-SDI");
			break;
		case FORWARD_SDI_MODE_QUAD_3G_SDI:
			pr_cont("Quad 3G-SDI");
			break;
		default:
			pr_cont("Unknown");
			return;
			break;
		}
		lines = t->sdi.active_height[0] + (t->sdi.interlaced ? t->sdi.active_height[1] : 0);
		if (lines == 487)
			lines = 480;
		interlaced = t->sdi.interlaced;
	} else if (t->type == FORWARD_TIMING_HDMI) {
		if (t->hdmi.type != V4L2_DV_BT_656_1120) {
			pr_cont("Unknown");
			return;
		}
		if (t->hdmi.bt.pixelclock <= 165000000)
			pr_cont("HDMI 1.0");
		else if (t->hdmi.bt.pixelclock <= 340000000)
			pr_cont("HDMI 1.4");
		else
			pr_cont("HDMI 2.0");
		lines = t->hdmi.bt.height;
		interlaced = t->hdmi.bt.interlaced ? true : false;
	}

	fi = forward_v4l2_frameinterval(t);
	if (!fi.numerator)
		fi.numerator = 1;

	if (interlaced)
		fi.denominator *= 2;

	fps_dec = fi.denominator / fi.numerator;
	fps_frac = (fi.denominator * 100 / fi.numerator) % 100;

	pr_cont(" %d%c%d", lines, interlaced ? 'i' : 'p', fps_dec);
	if (fps_frac)
		pr_cont(".%02d", fps_frac);
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 12, 0)
bool v4l2_match_dv_timings(const struct v4l2_dv_timings *t1, const struct v4l2_dv_timings *t2,
			   unsigned pclock_delta)
{
	if (t1->type != t2->type || t1->type != V4L2_DV_BT_656_1120)
		return false;
	if (t1->bt.width == t2->bt.width && t1->bt.height == t2->bt.height &&
	    t1->bt.interlaced == t2->bt.interlaced && t1->bt.polarities == t2->bt.polarities &&
	    t1->bt.pixelclock >= t2->bt.pixelclock - pclock_delta &&
	    t1->bt.pixelclock <= t2->bt.pixelclock + pclock_delta &&
	    t1->bt.hfrontporch == t2->bt.hfrontporch && t1->bt.vfrontporch == t2->bt.vfrontporch &&
	    t1->bt.vsync == t2->bt.vsync && t1->bt.vbackporch == t2->bt.vbackporch &&
	    (!t1->bt.interlaced ||
	     (t1->bt.il_vfrontporch == t2->bt.il_vfrontporch &&
	      t1->bt.il_vsync == t2->bt.il_vsync && t1->bt.il_vbackporch == t2->bt.il_vbackporch)))
		return true;
	return false;
}
#endif
