/*
   forward-v4l2-ctrl.c - v4l2 driver controls 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-v4l2-io.h"
#include "forward-v4l2-ctrl.h"
#include "forward-v4l2-genlock.h"

extern bool default_mute;

static int forward_v4l2_ctrl_s(struct v4l2_ctrl *ctrl)
{
	struct forward_v4l2_io *io = ctrl->priv;
	int result = 0;

	if (ctrl->id == V4L2_CID_FORWARD_ENABLE_VANC) {
		if (io->streaming)
			return -EBUSY;
		io->vbi_en = ctrl->val ? true : false;
	} else if (ctrl->id == V4L2_CID_FORWARD_WIDESCREEN) {
		io->widescreen = ctrl->val ? true : false;
		if (io->v4l2->ops->update_meta)
			io->v4l2->ops->update_meta(io);
	}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0)
	else if (ctrl->id == V4L2_CID_FORWARD_FREQUENCY_ADJUST) {
		*ctrl->p_new.p_s64 =
			io->v4l2->ops->set_clock_deviation(io->v4l2, *ctrl->p_new.p_s64);
	}
#else
	else if (ctrl->id == V4L2_CID_FORWARD_FREQUENCY_ADJUST) {
		ctrl->val64 = io->v4l2->ops->set_clock_deviation(io->v4l2, ctrl->val64);
	}
#endif
	else if (ctrl->id == V4L2_CID_FORWARD_ASI_PERIOD) {
		if (io->streaming)
			return -EBUSY;
		if (ctrl->val < 0 || ctrl->val > 3)
			return -EINVAL;
		io->asi_period = ctrl->val;

		if (io->v4l2->ops->check_fmt)
			io->v4l2->ops->check_fmt(io, &io->format);
		if (io->timings.sdi.flags & FORWARD_SDI_TIMINGS_F_ASI)
			io->v4l2->ops->timings_set(io, &io->timings);
	} else if (ctrl->id == V4L2_CID_FORWARD_TIMECODE) {
		if (io->streaming)
			return -EBUSY;
		if (ctrl->val < 0 || ctrl->val > 3)
			return -EINVAL;

		io->atc_type = ctrl->val;
	} else if (ctrl->id == V4L2_CID_FORWARD_GENLOCK_SOURCE) {
		if (ctrl->val < 0 ||
		    ctrl->val >= (V4L_FORWARD_GENLOCK_SRC_IN0 + io->v4l2->dev->cfg.io_count))
			return -EINVAL;

		if (ctrl->val >= V4L_FORWARD_GENLOCK_SRC_IN0) {
			int index = ctrl->val - V4L_FORWARD_GENLOCK_SRC_IN0;
			if (io->v4l2->io[index].state != FORWARD_IO_RX)
				return -EINVAL;
		}
		io->genlock->new_source = ctrl->val;
	} else if (ctrl->id == V4L2_CID_FORWARD_GENLOCK_OFFSET) {
		io->genlock->new_user_offset = ctrl->val;
	} else if (ctrl->id == V4L2_CID_FORWARD_ANALOG_RX_MODE) {
		result = io->v4l2->ops->analog_rx_set_mode(io->v4l2, ctrl->val);
	} else if (ctrl->id == V4L2_CID_FORWARD_CLONED_OUTPUT) {
		io->clone = ctrl->val ? true : false;
		if (io->v4l2->ops->toggle_clone)
			result = io->v4l2->ops->toggle_clone(io, io->clone);
	} else if (ctrl->id == V4L2_CID_FORWARD_BYPASS_DISABLE) {
		result = io->v4l2->ops->toggle_bypass(io, ctrl->val ? false : true,
						      V4L2_FORWARD_BYPASS_WATCHDOG_PERIOD_MS);
	} else if (ctrl->id == V4L2_CID_FORWARD_BYPASS_DISABLE_FORCE) {
		io->bypass_disabled = ctrl->val ? true : false;
		schedule_delayed_work(&io->bypass_work, 0);
	} else if (ctrl->id == V4L2_CID_FORWARD_MUTE_OUTPUT) {
		io->mute = ctrl->val ? true : false;
		io->manual_mute = true;
		io->v4l2->ops->toggle_mute(io, io->mute);
	} else if (ctrl->id == V4L2_CID_FORWARD_WATCHDOG_ENABLE) {
		io->watchdog_enabled = ctrl->val ? true : false;
		io->v4l2->ops->toggle_watchdog(io, io->watchdog_enabled);
	} else if (ctrl->id == V4L2_CID_FORWARD_WATCHDOG_KEEP_ALIVE) {
		io->v4l2->ops->watchdog_keepalive(io, ctrl->val);
	}

	return result;
}

static int forward_v4l2_ctrl_g(struct v4l2_ctrl *ctrl)
{
	struct forward_v4l2_io *io = ctrl->priv;
	int result = 0;

	if (ctrl->id == V4L2_CID_FORWARD_WIDESCREEN) {
		if (io->v4l2->ops->update_meta)
			io->v4l2->ops->update_meta(io);
		ctrl->val = io->widescreen;
	} else if (ctrl->id == V4L2_CID_DV_RX_POWER_PRESENT) {
		if (io->v4l2->ops->signal_present)
			ctrl->val = io->v4l2->ops->signal_present(io);
	} else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0)
		if (ctrl->id == V4L2_CID_FORWARD_HW_TIMESTAMP) {
		*ctrl->p_new.p_u32 = io->v4l2->ops->hw_timestamp(io->v4l2);
	} else if (ctrl->id == V4L2_CID_FORWARD_FREQUENCY_ADJUST) {
		*ctrl->p_new.p_s64 = io->v4l2->ops->clock_deviation(io->v4l2);
	}
#else
		if (ctrl->id == V4L2_CID_FORWARD_HW_TIMESTAMP) {
		ctrl->val = (s32)io->v4l2->ops->hw_timestamp(io->v4l2);
	} else if (ctrl->id == V4L2_CID_FORWARD_FREQUENCY_ADJUST) {
		ctrl->val64 = io->v4l2->ops->clock_deviation(io->v4l2);
	}
#endif
	else if (ctrl->id == V4L2_CID_FORWARD_ASI_PERIOD) {
		ctrl->val = io->asi_period;
	} else if (ctrl->id == V4L2_CID_FORWARD_TIMECODE) {
		ctrl->val = io->atc_type;
	} else if (ctrl->id == V4L2_CID_FORWARD_GENLOCK_SOURCE) {
		ctrl->val = io->genlock->source;
	} else if (ctrl->id == V4L2_CID_FORWARD_GENLOCK_STATE) {
		ctrl->val = io->genlock->state;
	} else if (ctrl->id == V4L2_CID_FORWARD_GENLOCK_OFFSET) {
		ctrl->val = io->genlock->new_user_offset;
	} else if (ctrl->id == V4L2_CID_FORWARD_ANALOG_RX_MODE) {
		ctrl->val = io->v4l2->ops->analog_rx_get_mode(io->v4l2);
	} else if (ctrl->id == V4L2_CID_FORWARD_ANALOG_RX_TIMESTAMP) {
		struct v4l2_forward_analog_rx_timestamp *ts =
			(struct v4l2_forward_analog_rx_timestamp *)ctrl->p_new.p;
		bool valid = 0;
		u32 timestamp = 0, cur_time = 0;
		u64 sys_time = 0;
		result = io->v4l2->ops->analog_rx_timestamp(io->v4l2, &valid, &timestamp, &cur_time,
							    &sys_time);
		ts->valid = valid;
		ts->timestamp = timestamp;
		ts->cur_hw_time = cur_time;
		ts->cur_sys_time = sys_time;
	} else if (ctrl->id == V4L2_CID_FORWARD_ANALOG_RX_TIMECODE) {
		struct v4l2_timecode *tc = (struct v4l2_timecode *)ctrl->p_new.p;
		bool valid;
		result = io->v4l2->ops->analog_rx_timecode(io->v4l2, &valid, tc);
		if (!valid)
			result = -ENODATA;
	} else if (ctrl->id == V4L2_CID_FORWARD_CLONED_OUTPUT) {
		ctrl->val = io->clone;
	} else if (ctrl->id == V4L2_CID_FORWARD_BYPASS_DISABLE_FORCE) {
		ctrl->val = io->bypass_disabled;
	} else if (ctrl->id == V4L2_CID_FORWARD_MUTE_OUTPUT) {
		ctrl->val = io->mute;
	} else if (ctrl->id == V4L2_CID_FORWARD_WATCHDOG_ENABLE) {
		ctrl->val = io->watchdog_enabled;
	}

	return result;
}

static const struct v4l2_ctrl_ops v4l2_ctrl_ops = {
	.s_ctrl = forward_v4l2_ctrl_s,
	.g_volatile_ctrl = forward_v4l2_ctrl_g,
};

static const struct v4l2_ctrl_config ctrl_vanc = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_ENABLE_VANC,
	.name = "Enable VANC",
	.type = V4L2_CTRL_TYPE_BOOLEAN,
	.min = 0,
	.max = 1,
	.step = 1,
};

static const struct v4l2_ctrl_config ctrl_widescreen = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_WIDESCREEN,
	.name = "Widescreen (16:9)",
	.type = V4L2_CTRL_TYPE_BOOLEAN,
	.min = 0,
	.max = 1,
	.step = 1,
};

static const char *const ctrl_asi_period_menu[] = {
	"20ms", "10ms", "5ms", "2.5ms", NULL,
};

static const struct v4l2_ctrl_config ctrl_asi_period = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_ASI_PERIOD,
	.name = "ASI period",
	.type = V4L2_CTRL_TYPE_MENU,
	.min = 0,
	.max = 3,
	.qmenu = ctrl_asi_period_menu,
};

static const struct v4l2_ctrl_config ctrl_hw_timestamp = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_HW_TIMESTAMP,
	.name = "Current hardware time",
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0)
	.type = V4L2_CTRL_TYPE_U32,
	.def = 0x0,
	.min = 0x0,
	.max = 0xFFFFFFFF,
	.step = 1,
	.dims = { 1 },
#else
	.type = V4L2_CTRL_TYPE_INTEGER,
	.def = INT_MIN,
	.min = INT_MIN,
	.max = INT_MAX,
	.step = 1,
#endif
};

static const struct v4l2_ctrl_config ctrl_freq_adjust = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_FREQUENCY_ADJUST,
	.name = "Frequency adjust (ppt)",
	.type = V4L2_CTRL_TYPE_INTEGER64,
	.def = 0,
	.min = -500000000LL,
	.max = 500000000LL,
	.step = 1,
};

static const char *const ctrl_tc_type_menu[] = { "None", "LTC", "VITC", "Any", NULL };

static const struct v4l2_ctrl_config ctrl_tc_type = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_TIMECODE,
	.name = "Timecode type",
	.type = V4L2_CTRL_TYPE_MENU,
	.min = 0,
	.max = 3,
	.qmenu = ctrl_tc_type_menu,
};

static const char *const ctrl_gl_source_menu[] = { "Master", "Analog", "IN1", "IN2", "IN3", "IN4",
						   "IN5",    "IN6",    "IN7", "IN8", NULL };

static const struct v4l2_ctrl_config ctrl_gl_source = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_GENLOCK_SOURCE,
	.name = "Genlock source",
	.type = V4L2_CTRL_TYPE_MENU,
	.min = 0,
	.max = 9,
	.qmenu = ctrl_gl_source_menu,
};

static const char *const ctrl_gl_state_menu[] = { "Master", "No input", "Locking",
						  "Locked", "Holdover", NULL };

static const struct v4l2_ctrl_config ctrl_gl_state = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_GENLOCK_STATE,
	.name = "Genlock state",
	.type = V4L2_CTRL_TYPE_MENU,
	.min = 0,
	.max = 4,
	.qmenu = ctrl_gl_state_menu,
};

static const struct v4l2_ctrl_config ctrl_gl_offset = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_GENLOCK_OFFSET,
	.name = "Genlock offset (6.734ns)",
	.type = V4L2_CTRL_TYPE_INTEGER,
	.def = 0,
	.min = -6187500,
	.max = 6187500,
	.step = 1,
};

static const struct v4l2_ctrl_config ctrl_cloned_output = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_CLONED_OUTPUT,
	.name = "Cloned output",
	.type = V4L2_CTRL_TYPE_BOOLEAN,
	.min = 0,
	.max = 1,
	.step = 1,
};

static const struct v4l2_ctrl_config ctrl_bypass_disable = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_BYPASS_DISABLE,
	.name = "Bypass disable",
	.type = V4L2_CTRL_TYPE_BOOLEAN,
	.min = 0,
	.max = 1,
	.step = 1,
};

static const struct v4l2_ctrl_config ctrl_bypass_disable_force = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_BYPASS_DISABLE_FORCE,
	.name = "Bypass disable forced",
	.type = V4L2_CTRL_TYPE_BOOLEAN,
	.min = 0,
	.max = 1,
	.step = 1,
};

static const char *const ctrl_analog_rx_mode_menu[] = { "Genlock", "PPS", "LTC" };

static const struct v4l2_ctrl_config ctrl_analog_rx_mode = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_ANALOG_RX_MODE,
	.name = "Analog input mode",
	.type = V4L2_CTRL_TYPE_MENU,
	.min = 0,
	.max = 2,
	.qmenu = ctrl_analog_rx_mode_menu,
};

static const struct v4l2_ctrl_config ctrl_analog_rx_timestamp = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_ANALOG_RX_TIMESTAMP,
	.name = "Analog input timestamp",
	.type = V4L2_CTRL_TYPE_FORWARD_ANALOG_RX_TIMESTAMP,
	.elem_size = sizeof(struct v4l2_forward_analog_rx_timestamp)
};

static const struct v4l2_ctrl_config ctrl_analog_rx_timecode = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_ANALOG_RX_TIMECODE,
	.name = "Analog input timecode",
	.type = V4L2_CTRL_TYPE_FORWARD_TIMECODE,
	.elem_size = sizeof(struct v4l2_timecode)
};

static const struct v4l2_ctrl_config ctrl_mute_1 = { .ops = &v4l2_ctrl_ops,
						     .id = V4L2_CID_FORWARD_MUTE_OUTPUT,
						     .name = "Mute output",
						     .type = V4L2_CTRL_TYPE_BOOLEAN,
						     .min = 0,
						     .max = 1,
						     .step = 1,
						     .def = 1 };

static const struct v4l2_ctrl_config ctrl_mute_0 = { .ops = &v4l2_ctrl_ops,
						     .id = V4L2_CID_FORWARD_MUTE_OUTPUT,
						     .name = "Mute output",
						     .type = V4L2_CTRL_TYPE_BOOLEAN,
						     .min = 0,
						     .max = 1,
						     .step = 1,
						     .def = 0 };

static const struct v4l2_ctrl_config ctrl_watchdog_enable = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_WATCHDOG_ENABLE,
	.name = "Watchdog enable",
	.type = V4L2_CTRL_TYPE_BOOLEAN,
	.min = 0,
	.max = 1,
	.step = 1,
};

static const struct v4l2_ctrl_config ctrl_watchdog_keep_alive = {
	.ops = &v4l2_ctrl_ops,
	.id = V4L2_CID_FORWARD_WATCHDOG_KEEP_ALIVE,
	.name = "Watchdog keep alive",
	.type = V4L2_CTRL_TYPE_INTEGER,
	.min = 0,
	.max = 65535,
	.step = 1,
};

void forward_v4l2_ctrl_deinit(struct forward_v4l2_io_dev *iodev)
{
	if (iodev->vdev.ctrl_handler)
		v4l2_ctrl_handler_free(iodev->vdev.ctrl_handler);
	iodev->vdev.ctrl_handler = 0;
}

int forward_v4l2_ctrl_init_out(struct forward_v4l2_io_dev *iodev)
{
	struct v4l2_ctrl_handler *ctl = &iodev->ctl;
	struct v4l2_ctrl *c;

	if (v4l2_ctrl_handler_init(ctl, 2))
		return -ENOMEM;

	v4l2_ctrl_new_std(ctl, &v4l2_ctrl_ops, V4L2_CID_MIN_BUFFERS_FOR_OUTPUT,
			  FORWARD_V4L2_MIN_BUFFERS, FORWARD_V4L2_MIN_BUFFERS, 1,
			  FORWARD_V4L2_MIN_BUFFERS);

	if (iodev->io->v4l2->ops->vbi_support)
		v4l2_ctrl_new_custom(ctl, &ctrl_vanc, iodev->io);
	if (iodev->io->v4l2->ops->update_meta)
		v4l2_ctrl_new_custom(ctl, &ctrl_widescreen, iodev->io);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0)
	if (iodev->io->v4l2->ops->hw_timestamp) {
		c = v4l2_ctrl_new_custom(ctl, &ctrl_hw_timestamp, iodev->io);
		if (c) {
			c->flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE;
		}
	}

	if (iodev->io->v4l2->ops->clock_deviation && iodev->io->v4l2->ops->set_clock_deviation) {
		c = v4l2_ctrl_new_custom(ctl, &ctrl_freq_adjust, iodev->io);
		if (c)
			c->flags |= V4L2_CTRL_FLAG_EXECUTE_ON_WRITE | V4L2_CTRL_FLAG_VOLATILE;
	}
#endif
	c = v4l2_ctrl_new_custom(ctl, &ctrl_asi_period, iodev->io);

	if (iodev->io->v4l2->ops->update_timecode) {
		v4l2_ctrl_new_custom(ctl, &ctrl_tc_type, iodev->io);
	}
	if (iodev->io->v4l2->ops->genlock_switch) {
		c = v4l2_ctrl_new_custom(ctl, &ctrl_gl_source, iodev->io);
		if (c)
			c->flags |= V4L2_CTRL_FLAG_EXECUTE_ON_WRITE | V4L2_CTRL_FLAG_VOLATILE;

		c = v4l2_ctrl_new_custom(ctl, &ctrl_gl_state, iodev->io);
		if (c)
			c->flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE;
	}
	if (iodev->io->v4l2->ops->genlock_sync_offset) {
		c = v4l2_ctrl_new_custom(ctl, &ctrl_gl_offset, iodev->io);
		if (c)
			c->flags |= V4L2_CTRL_FLAG_EXECUTE_ON_WRITE | V4L2_CTRL_FLAG_VOLATILE;
	}
	if (iodev->io->v4l2->ops->analog_rx_set_mode) {
		c = v4l2_ctrl_new_custom(ctl, &ctrl_analog_rx_mode, iodev->io);
		c = v4l2_ctrl_new_custom(ctl, &ctrl_analog_rx_timestamp, iodev->io);
		if (c) {
			c->flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE;
		}
		c = v4l2_ctrl_new_custom(ctl, &ctrl_analog_rx_timecode, iodev->io);
		if (c) {
			c->flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE;
		}
	}
	if (iodev->io->v4l2->ops->toggle_clone) {
		v4l2_ctrl_new_custom(ctl, &ctrl_cloned_output, iodev->io);
	}
	if (iodev->io->v4l2->ops->toggle_bypass) {
		c = v4l2_ctrl_new_custom(ctl, &ctrl_bypass_disable, iodev->io);
		if (c)
			c->flags |= V4L2_CTRL_FLAG_EXECUTE_ON_WRITE | V4L2_CTRL_FLAG_WRITE_ONLY;
		c = v4l2_ctrl_new_custom(ctl, &ctrl_bypass_disable_force, iodev->io);
	}

	if (iodev->io->v4l2->ops->toggle_mute) {
		if (default_mute)
			c = v4l2_ctrl_new_custom(ctl, &ctrl_mute_1, iodev->io);
		else
			c = v4l2_ctrl_new_custom(ctl, &ctrl_mute_0, iodev->io);
		if (c)
			c->flags |= V4L2_CTRL_FLAG_EXECUTE_ON_WRITE | V4L2_CTRL_FLAG_VOLATILE;
	}

	if (iodev->io->v4l2->ops->toggle_watchdog) {
		c = v4l2_ctrl_new_custom(ctl, &ctrl_watchdog_enable, iodev->io);
		c = v4l2_ctrl_new_custom(ctl, &ctrl_watchdog_keep_alive, iodev->io);
		if (c)
			c->flags |= V4L2_CTRL_FLAG_EXECUTE_ON_WRITE | V4L2_CTRL_FLAG_WRITE_ONLY;
	}

	if (ctl->error)
		goto fail;

	iodev->vdev.ctrl_handler = ctl;
	v4l2_ctrl_handler_setup(ctl);

	return 0;

fail:
	v4l2_ctrl_handler_free(ctl);
	return ctl->error;
}

int forward_v4l2_ctrl_init_cap(struct forward_v4l2_io_dev *iodev)
{
	struct v4l2_ctrl_handler *ctl = &iodev->ctl;
	struct v4l2_ctrl *c;

	if (v4l2_ctrl_handler_init(ctl, 2))
		return -ENOMEM;

	v4l2_ctrl_new_std(ctl, &v4l2_ctrl_ops, V4L2_CID_MIN_BUFFERS_FOR_CAPTURE,
			  FORWARD_V4L2_MIN_BUFFERS, FORWARD_V4L2_MIN_BUFFERS, 1,
			  FORWARD_V4L2_MIN_BUFFERS);

	c = v4l2_ctrl_new_std(ctl, &v4l2_ctrl_ops, V4L2_CID_DV_RX_POWER_PRESENT, 0, 1, 0, 0);
	if (c) {
		c->flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE;
		c->priv = iodev->io;
	}

	if (iodev->io->v4l2->ops->vbi_support)
		v4l2_ctrl_new_custom(ctl, &ctrl_vanc, iodev->io);

	if (iodev->io->v4l2->ops->update_meta) {
		c = v4l2_ctrl_new_custom(ctl, &ctrl_widescreen, iodev->io);
		if (c) {
			c->flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE;
		}
	}

	if (iodev->io->v4l2->ops->hw_timestamp) {
		c = v4l2_ctrl_new_custom(ctl, &ctrl_hw_timestamp, iodev->io);
		if (c) {
			c->flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE;
		}
	}

	if (iodev->io->v4l2->ops->clock_deviation && iodev->io->v4l2->ops->set_clock_deviation) {
		c = v4l2_ctrl_new_custom(ctl, &ctrl_freq_adjust, iodev->io);
		if (c)
			c->flags |= V4L2_CTRL_FLAG_EXECUTE_ON_WRITE | V4L2_CTRL_FLAG_VOLATILE;
	}

	c = v4l2_ctrl_new_custom(ctl, &ctrl_asi_period, iodev->io);

	if (iodev->io->v4l2->ops->update_timecode) {
		v4l2_ctrl_new_custom(ctl, &ctrl_tc_type, iodev->io);
	}
	if (iodev->io->v4l2->ops->analog_rx_set_mode) {
		c = v4l2_ctrl_new_custom(ctl, &ctrl_analog_rx_mode, iodev->io);
		c = v4l2_ctrl_new_custom(ctl, &ctrl_analog_rx_timestamp, iodev->io);
		if (c) {
			c->flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE;
		}
		c = v4l2_ctrl_new_custom(ctl, &ctrl_analog_rx_timecode, iodev->io);
		if (c) {
			c->flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE;
		}
	}
	if (iodev->io->v4l2->ops->toggle_bypass) {
		c = v4l2_ctrl_new_custom(ctl, &ctrl_bypass_disable, iodev->io);
		if (c)
			c->flags |= V4L2_CTRL_FLAG_EXECUTE_ON_WRITE | V4L2_CTRL_FLAG_WRITE_ONLY;
		c = v4l2_ctrl_new_custom(ctl, &ctrl_bypass_disable_force, iodev->io);
	}

	if (ctl->error)
		goto fail;

	iodev->vdev.ctrl_handler = ctl;
	v4l2_ctrl_handler_setup(ctl);

	return 0;

fail:
	v4l2_ctrl_handler_free(ctl);
	return ctl->error;
}
