/*
   forward-v4l2-fd922.c - v4l2 driver for SoftLab-NSK FD922 video board

   Copyright (C) 2017 - 2023 Konstantin Oblaukhov <oblaukhov@sl.iae.nsk.su>

   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.h"
#include "forward-v4l2-io.h"

#include <linux/delay.h>
#include "forward-v4l2-ioctl.h"
#include "forward-pll.h"

#include "forward-v4l2-genlock.h"

#include "fd922_reg.h"

#define FD922_IS_VERSION_4(dev) (dev->pci_dev->device == 0x0025)

#define FD922_BUFFER_NUMBER(in, irq) (((irq) >> ((in) * 3 + 1)) & 0x3)
#define FD922_BUFFER_NEXT(buf, offset, count) ((((unsigned int)(buf)) + (offset)) % (count))
#define FD922_NUMBER_IN_BUFFERS (4)
#define FD922_NUMBER_OUT_BUFFERS (2)

#define FD922_GENLOCK_ANALOG_DELAY 300
#define FD922_GENLOCK_SD_DELAY 572
#define FD922_GENLOCK_HD_DELAY 216
#define FD922_GENLOCK_3G_DELAY 110

// 50 buffers per second at 270 Mbit/s
#define FD922_ASI_IN_BUFFER_SIZE (270000000ULL / 10 / 50)
#define FD922_ASI_IN_BUFFER_SIZE_RAW (270000000ULL / 8 / 50)

#define FD922_ASI_PERIOD_REVISION 54
#define FD922_GENLOCK_0H_REVISION 68

#define FD922_FIRMWARE_REVISION(dev) (dev->fw_version & 0xFFFF)

static u32 fd922_irq_mask(const struct forward_v4l2_io *io)
{
	return (1 << (io->index * 3));
}

inline bool fd922_irq_info_in(struct forward_v4l2_io *io, u32 irq, int *cur_buf, int *prev_buf,
			      int *next_buf, int *vbi_size, int *video_size)
{
	if (!(irq & (1 << io->index * 3)))
		return false;

	*cur_buf = FD922_BUFFER_NUMBER(io->index, irq);
	*prev_buf = FD922_BUFFER_NEXT(*cur_buf, -1, FD922_NUMBER_IN_BUFFERS);
	*next_buf = FD922_BUFFER_NEXT(*cur_buf, 1, FD922_NUMBER_IN_BUFFERS);
	if (video_size != NULL)
		*video_size = 0;
	if (vbi_size != NULL)
		*vbi_size = 0;

	return true;
}

inline bool fd922_irq_info_out(struct forward_v4l2_io *io, u32 irq, int *cur_buf, int *prev_buf,
			       int *next_buf, int *vbi_size, int *video_size)
{
	if (!(irq & (1 << io->index * 3)))
		return false;

	*cur_buf = FD922_BUFFER_NUMBER(io->index, irq);
	*prev_buf = FD922_BUFFER_NEXT(*cur_buf, -1, FD922_NUMBER_OUT_BUFFERS);
	*next_buf = FD922_BUFFER_NEXT(*cur_buf, 1, FD922_NUMBER_OUT_BUFFERS);
	if (video_size != NULL)
		*video_size = 0;
	if (vbi_size != NULL)
		*vbi_size = 0;

	return true;
}

inline bool fd922_irq_info(struct forward_v4l2_io *io, u32 irq, int *cur_buf, int *prev_buf,
			   int *next_buf, int *vbi_size, int *video_size)
{
	if (io->state == FORWARD_IO_TX)
		return fd922_irq_info_out(io, irq, cur_buf, prev_buf, next_buf, vbi_size,
					  video_size);
	else
		return fd922_irq_info_in(io, irq, cur_buf, prev_buf, next_buf, vbi_size,
					 video_size);
}

static void fd922_region_info(const struct forward_v4l2_io *io, u32 *addr, size_t *size)
{
	*addr = io->index * 0x20000000;
	*size = 0x10000000;
}

static void fd922_buffer_info(const struct forward_v4l2_io *io, int buffer, u32 *address,
			      size_t *size)
{
	int num = io->index;

	*size = 0x04000000UL;
	*address = num * 0x20000000UL + buffer * *size;
}

static bool fd922_timings_valid(const struct forward_v4l2_io *io,
				const struct forward_v4l2_timings *t)
{
	if (t->type != FORWARD_TIMING_SDI)
		return false;

	if ((t->sdi.mode != FORWARD_SDI_MODE_12G_SDI) && (t->sdi.mode != FORWARD_SDI_MODE_6G_SDI) &&
	    (t->sdi.mode != FORWARD_SDI_MODE_3G_SDI) && (t->sdi.mode != FORWARD_SDI_MODE_HD_SDI) &&
	    (t->sdi.mode != FORWARD_SDI_MODE_SD_SDI)) {
		return false;
	}

	return true;
}

static int fd922_timings_query(const struct forward_v4l2_io *io, struct forward_v4l2_timings *t)
{
	struct forward_dev *dev = io->v4l2->dev;
	enum forward_sdi_mode mode;
	FD922_VideoInCS ics = FD922_VideoInCS_R(dev->csr, io->index);
	FD922_VideoInLine line;
	FD922_VideoInPixel pixel;
	FD922_VideoInVPID vpid = FD922_VideoInVPID_R(dev->csr, io->index);
	FD922_VideoInFrameSize framesize = FD922_VideoInFrameSize_R(dev->csr, io->index);
	u32 pixel_clk;
	int width, height, active_width, active_height;
	bool result;

	if (io->state != FORWARD_IO_RX)
		return -ENOTSUPP;

	if (!ics.cd)
		return -ENOLINK;

	if (!ics.modeLocked || !ics.formatLocked)
		return -ENOLCK;

	switch (ics.mode) {
	case 0:
		mode = FORWARD_SDI_MODE_HD_SDI;
		break;
	case 1:
		mode = FORWARD_SDI_MODE_3G_SDI;
		break;
	case 2:
		mode = FORWARD_SDI_MODE_6G_SDI;
		break;
	case 3:
		mode = FORWARD_SDI_MODE_12G_SDI;
		break;
	case 4:
		mode = FORWARD_SDI_MODE_SD_SDI;
		break;
	default:
		mode = FORWARD_SDI_MODE_INVALID;
		break;
	}

	if (ics.mode == 5) {
		int i = 0;
		for (i = 0; i < forward_v4l2_timings_count; i++) {
			if (fd922_timings_valid(io, &forward_v4l2_timings[i])) {
				*t = forward_v4l2_timings[i];
				t->sdi.flags |= FORWARD_SDI_TIMINGS_F_ASI;
				break;
			}
		}
		return 0;
	}

	line = FD922_VideoInLine_R(dev->csr, io->index);
	pixel = FD922_VideoInPixel_R(dev->csr, io->index);
	vpid = FD922_VideoInVPID_R(dev->csr, io->index);
	if (!framesize.size)
		framesize.size = 1;
	width = pixel.totalPixels;
	height = line.totalLines;
	active_width = pixel.activePixels;
	active_height = line.activeLines;

	if ((mode == FORWARD_SDI_MODE_6G_SDI) || (mode == FORWARD_SDI_MODE_12G_SDI)) {
		width *= 2;
		height *= 2;
		active_width *= 2;
		active_height *= 2;
	}

	pixel_clk = 36000000ULL * width * height / framesize.size;

	result = forward_v4l2_timing_guess_sdi(mode, !ics.progressive, width, height, active_width,
					       active_height, vpid.vpid, pixel_clk, t);

	return result ? 0 : -ERANGE;
}

inline int fd922_timings_set_in(const struct forward_v4l2_io *io,
				const struct forward_v4l2_timings *t)
{
	if (!fd922_timings_valid(io, t))
		return -ERANGE;

	return 0;
}

inline int fd922_timings_set_out(const struct forward_v4l2_io *io,
				 const struct forward_v4l2_timings *t)
{
	// TODO: ASI
	struct forward_dev *dev = io->v4l2->dev;
	int index = io->index - 2;
	FD922_VideoOutCS ocs = FD922_VideoOutCS_R(dev->csr, index);
	FD922_VideoOutLine line;
	FD922_VideoOutPixel pixel;
	FD922_VideoOutStart start;
	FD922_VideoOutStop stop;
	FD922_VideoOutField field;
	FD922_VideoOutVPID vpid;
	bool dq;
	bool asi = t->sdi.flags & FORWARD_SDI_TIMINGS_F_ASI;
	int dq_div = 1;

	memset(&ocs, 0, sizeof(FD922_VideoOutCS));

	ocs.reset = 1;
	FD922_VideoOutCS_W(dev->csr, index, ocs);

	dq = !asi &&
	     ((t->sdi.mode == FORWARD_SDI_MODE_6G_SDI) | (t->sdi.mode == FORWARD_SDI_MODE_12G_SDI));

	ocs.levelB = 0;

	switch (t->sdi.mode) {
	case FORWARD_SDI_MODE_HD_SDI:
		ocs.mode = 0;
		break;
	case FORWARD_SDI_MODE_3G_SDI:
		ocs.mode = 1;
		break;
	case FORWARD_SDI_MODE_6G_SDI:
		ocs.mode = 2;
		break;
	case FORWARD_SDI_MODE_12G_SDI:
		ocs.mode = 3;
		break;
	case FORWARD_SDI_MODE_SD_SDI:
		ocs.mode = 4;
		break;
	default:
		ocs.mode = 0;
		break;
	}

	if (asi)
		ocs.mode = 5;

	if (t->sdi.m)
		ocs.clockM = 1;

	ocs.freeRunning = 1;
	ocs.progressive = (asi || t->sdi.interlaced) ? 0 : 1;

	if (dq)
		dq_div = 2;

	if (asi) {
		line.totalLines = 540;
		pixel.totalPixels = 1000 >> io->asi_period;
		pixel.activePixels = 720 >> io->asi_period;
		start.startOdd = 11;
		start.startEven = 281;
		stop.stopOdd = 267;
		stop.stopEven = 537;
		field.switchOdd = 539;
		field.switchEven = 269;
		vpid.vpid = 0x00000000;
	} else {
		line.totalLines = t->sdi.height / dq_div;
		pixel.totalPixels = t->sdi.width / dq_div;
		pixel.activePixels = t->sdi.active_width / dq_div;
		start.startOdd = t->sdi.active_starts[0] / dq_div;
		start.startEven = t->sdi.active_starts[1] / dq_div;
		stop.stopOdd = t->sdi.active_stops[0] / dq_div;
		stop.stopEven = t->sdi.active_stops[1] / dq_div;
		field.switchOdd = t->sdi.field_switch[0] / dq_div;
		field.switchEven = t->sdi.field_switch[1] / dq_div;
		vpid.vpid = forward_v4l2_timings_vpid(t, false, false, io->widescreen);
	}

	FD922_VideoOutLine_W(dev->csr, index, line);
	FD922_VideoOutPixel_W(dev->csr, index, pixel);
	FD922_VideoOutStart_W(dev->csr, index, start);
	FD922_VideoOutStop_W(dev->csr, index, stop);
	FD922_VideoOutField_W(dev->csr, index, field);
	FD922_VideoOutVPID_W(dev->csr, index, vpid);
	FD922_VideoOutCS_W(dev->csr, index, ocs);

	if (asi)
		ocs.playbackRaw = 1;
	else {
		bool gl_correct =
			forward_v4l2_timings_fps_divisible(t, &io->v4l2->genlock->timings);
		ocs.freeRunning = (io->sync && gl_correct) ? 0 : 1;
	}

	FD922_VideoOutCS_W(dev->csr, index, ocs);

	ocs.reset = 0;

	FD922_VideoOutCS_W(dev->csr, index, ocs);

	return 0;
}

static int fd922_timings_set(const struct forward_v4l2_io *io, const struct forward_v4l2_timings *t)
{
	if (io->index >= 2)
		return fd922_timings_set_out(io, t);
	else
		return fd922_timings_set_in(io, t);
}

static bool fd922_timings_changed(const struct forward_v4l2_io *io,
				  const struct forward_v4l2_timings *old,
				  struct forward_v4l2_timings *new)
{
	if (fd922_timings_query(io, new))
		return false;

	if ((old->sdi.flags & FORWARD_SDI_TIMINGS_F_ASI) ?
		    !(new->sdi.flags &FORWARD_SDI_TIMINGS_F_ASI) :
		    (new->sdi.flags &FORWARD_SDI_TIMINGS_F_ASI))
		return true;

	if (old->sdi.mode != new->sdi.mode)
		return true;

	return !forward_v4l2_timings_equal(new, old, true);
}

static void fd922_check_fmt(const struct forward_v4l2_io *io, struct v4l2_pix_format *f)
{
	struct forward_dev *dev = io->v4l2->dev;
	FD922_VideoInCS ics;

	if ((f->pixelformat != V4L2_PIX_FMT_MPEG) && (f->pixelformat != V4L2_PIX_FMT_UYVY) &&
	    (f->pixelformat != V4L2_PIX_FMT_YUYV) && (f->pixelformat != V4L2_PIX_FMT_V210) &&
	    (f->pixelformat != V4L2_PIX_FMT_SL_RAW) &&
	    (f->pixelformat != V4L2_PIX_FMT_SL_RAW_ASI)) {
		f->pixelformat = V4L2_PIX_FMT_UYVY;
		f->bytesperline = f->width * 2;
		f->sizeimage = f->height * f->bytesperline;
	}

	if (f->pixelformat == V4L2_PIX_FMT_MPEG) {
		f->bytesperline = 0;
		if (FD922_FIRMWARE_REVISION(dev) >= FD922_ASI_PERIOD_REVISION)
			f->sizeimage = FD922_ASI_IN_BUFFER_SIZE >> (io->asi_period);
		else
			f->sizeimage = FD922_ASI_IN_BUFFER_SIZE;
	} else if (f->pixelformat == V4L2_PIX_FMT_SL_RAW_ASI) {
		f->bytesperline = 0;
		if (FD922_FIRMWARE_REVISION(dev) >= FD922_ASI_PERIOD_REVISION)
			f->sizeimage = FD922_ASI_IN_BUFFER_SIZE_RAW >> (io->asi_period);
		else
			f->sizeimage = FD922_ASI_IN_BUFFER_SIZE_RAW;
	}

	if (io->state != FORWARD_IO_RX)
		return;

	ics = FD922_VideoInCS_R(dev->csr, io->index);

	if (!ics.cd || !ics.formatLocked || !ics.modeLocked)
		return;

	if (ics.mode == 5) {
		if (f->pixelformat == V4L2_PIX_FMT_SL_RAW)
			f->pixelformat = V4L2_PIX_FMT_SL_RAW_ASI;
		else if (f->pixelformat != V4L2_PIX_FMT_SL_RAW_ASI)
			f->pixelformat = V4L2_PIX_FMT_MPEG;

		if (f->pixelformat == V4L2_PIX_FMT_MPEG) {
			f->bytesperline = 0;
			if (FD922_FIRMWARE_REVISION(dev) >= FD922_ASI_PERIOD_REVISION)
				f->sizeimage = FD922_ASI_IN_BUFFER_SIZE >> (io->asi_period);
			else
				f->sizeimage = FD922_ASI_IN_BUFFER_SIZE;
		} else if (f->pixelformat == V4L2_PIX_FMT_SL_RAW) {
			f->bytesperline = 0;
			if (FD922_FIRMWARE_REVISION(dev) >= FD922_ASI_PERIOD_REVISION)
				f->sizeimage = FD922_ASI_IN_BUFFER_SIZE_RAW >> (io->asi_period);
			else
				f->sizeimage = FD922_ASI_IN_BUFFER_SIZE_RAW;
		}
	} else {
		if (f->pixelformat == V4L2_PIX_FMT_SL_RAW_ASI)
			f->pixelformat = V4L2_PIX_FMT_SL_RAW;

		if (f->pixelformat == V4L2_PIX_FMT_MPEG)
			f->pixelformat = V4L2_PIX_FMT_UYVY;

		if (f->pixelformat == V4L2_PIX_FMT_V210) {
			f->bytesperline = f->width * 8 / 3;
			f->bytesperline = ((f->bytesperline - 1) / 128 + 1) * 128;
		} else
			f->bytesperline = f->width * 2;

		f->sizeimage = f->height * f->bytesperline;
	}
}

static int fd922_enum_fmt(const struct forward_v4l2_io *io, int index, u32 *pix_fmt, char *desc,
			  size_t desc_len)
{
	(void)io;

	if (io->state == FORWARD_IO_RX) {
		switch (index) {
		case 0:
			*pix_fmt = V4L2_PIX_FMT_UYVY;
			snprintf(desc, desc_len, "UYVY 4:2:2");
			break;
		case 1:
			*pix_fmt = V4L2_PIX_FMT_YUYV;
			snprintf(desc, desc_len, "YUYV 4:2:2");
			break;
		case 2:
			*pix_fmt = V4L2_PIX_FMT_MPEG;
			snprintf(desc, desc_len, "MPEG-TS");
			break;
		case 3:
			*pix_fmt = V4L2_PIX_FMT_V210;
			snprintf(desc, desc_len, "YUYV 4:2:2 v210");
			break;
		case 4:
			*pix_fmt = V4L2_PIX_FMT_SL_RAW;
			snprintf(desc, desc_len, "SoftLab-NSK Raw");
			break;
		case 5:
			*pix_fmt = V4L2_PIX_FMT_SL_RAW_ASI;
			snprintf(desc, desc_len, "SoftLab-NSK Raw ASI");
			break;
		default:
			return -EINVAL;
		}
	} else {
		switch (index) {
		case 0:
			*pix_fmt = V4L2_PIX_FMT_UYVY;
			snprintf(desc, desc_len, "UYVY 4:2:2");
			break;
		case 1:
			*pix_fmt = V4L2_PIX_FMT_MPEG;
			snprintf(desc, desc_len, "MPEG-TS");
			break;
		case 2:
			*pix_fmt = V4L2_PIX_FMT_V210;
			snprintf(desc, desc_len, "YUYV 4:2:2 v210");
			break;
		case 3:
			*pix_fmt = V4L2_PIX_FMT_SL_RAW;
			snprintf(desc, desc_len, "SoftLab-NSK Raw");
			break;
		case 4:
			*pix_fmt = V4L2_PIX_FMT_SL_RAW_ASI;
			snprintf(desc, desc_len, "SoftLab-NSK Raw ASI");
			break;
		default:
			return -EINVAL;
		}
	}

	return 0;
}

static int fd922_update_meta(struct forward_v4l2_io *io)
{
	struct forward_dev *dev = io->v4l2->dev;

	if (io->index < 2) {
		FD922_VideoInVPID vpid = FD922_VideoInVPID_R(dev->csr, io->index);

		if (((vpid.vpid >> 24) & 0xFF) == 0x81)
			io->widescreen = (vpid.vpid & 0x8000) ? true : false;
		else
			io->widescreen = true;
	} else {
		FD922_VideoOutVPID vpid = FD922_VideoOutVPID_R(dev->csr, io->index - 2);

		if (((vpid.vpid >> 24) & 0xFF) == 0x81) {
			if (io->widescreen)
				vpid.vpid |= 0x8000;
			else
				vpid.vpid &= ~(0x8000);

			FD922_VideoOutVPID_W(dev->csr, io->index - 2, vpid);
		}
	}

	return 0;
}

static void fd922_update_timecode(struct forward_v4l2_io *io, u64 timecode, u16 ddb)
{
	uint32_t *reg = io->v4l2->dev->csr + FD922_VideoOutATC_A(io->index - 2);
	iowrite32((0 << 30) | ((timecode >> 0) & 0x3FFFFFFF), reg);
	iowrite32((1 << 30) | ((timecode >> 30) & 0x3FFFFFFF), reg);
	iowrite32((2 << 30) | ((timecode >> 60) & 0xF) | (((u32)ddb << 4) & 0xFFF0), reg);
}

static void fd922_prepare(struct forward_v4l2_io *io)
{
	struct forward_dev *dev = io->v4l2->dev;

	if (io->index < 2) {
		FD922_VideoInCS ics = FD922_VideoInCS_R(dev->csr, io->index);

		ics.test = 0;
		ics.vbi10b = 1;
		ics.vis10b = (io->format.pixelformat == V4L2_PIX_FMT_V210) ? 1 : 0;
		ics.vbiTop = io->vbi_en ? 1 : 0;
		ics.vbiBottom = 0;
		ics.captureRaw = (io->asi || io->raw) ? 1 : 0;
		if (FD922_FIRMWARE_REVISION(dev) >= FD922_ASI_PERIOD_REVISION)
			ics.asiPeriod = io->asi_period;
		ics.ycSwap = (io->format.pixelformat == V4L2_PIX_FMT_YUYV) ? 1 : 0;

		io->format_conv = FORWARD_V4L2_CONV_NONE;

		FD922_VideoInCS_W(dev->csr, io->index, ics);
	} else {
		FD922_VideoOutCS ocs = FD922_VideoOutCS_R(dev->csr, io->index - 2);
		FD922_VideoOutOddDataCount oddC;
		FD922_VideoOutEvenDataCount evenC;
		struct forward_v4l2_timings *t = &io->timings;
		int dq_div;

		ocs.playbackRaw = (io->asi || io->raw) ? 1 : 0;
		ocs.vbiTop = io->vbi_en ? 1 : 0;
		ocs.vbiBottom = 0;
		ocs.vbi10b = 1;
		ocs.vis10b = (io->format.pixelformat == V4L2_PIX_FMT_V210) ? 1 : 0;
		ocs.dataPresent = 1;
		ocs.test = 0;
		ocs.clone = 0;
		ocs.atcEnable = (io->atc_type != V4L2_FORWARD_TIMECODE_DISABLED) ? 1 : 0;
		ocs.atcType = (io->atc_type == V4L2_FORWARD_TIMECODE_LTC) ? 0 : 1;

		dq_div = (!io->asi && ((t->sdi.mode == FORWARD_SDI_MODE_6G_SDI) |
				       (t->sdi.mode == FORWARD_SDI_MODE_12G_SDI))) ?
				 4 :
				 1;

		if (io->asi) {
			oddC.count = 675000;
			evenC.count = 675000;
		} else if (io->raw) {
			oddC.count = (t->sdi.field_switch[1] + 1) * t->sdi.width * 20 / 8;
			evenC.count = (t->sdi.field_switch[0] - t->sdi.field_switch[1]) *
				      t->sdi.width * 20 / 8;
			oddC.count /= dq_div;
			evenC.count /= dq_div;
		} else {
			if (io->format.pixelformat != V4L2_PIX_FMT_V210) {
				oddC.count = t->sdi.active_height[0] * t->sdi.active_width * 2;
				evenC.count = t->sdi.active_height[1] * t->sdi.active_width * 2;
			} else {
				oddC.count = t->sdi.active_height[0] * t->sdi.active_width * 20 / 8;
				evenC.count =
					t->sdi.active_height[1] * t->sdi.active_width * 20 / 8;
			}

			if (io->vbi_en) {
				oddC.count += t->sdi.topVBIs[0] * t->sdi.active_width * 20 / 8;
				evenC.count += t->sdi.topVBIs[1] * t->sdi.active_width * 20 / 8;
			}
			oddC.count /= dq_div;
			evenC.count /= dq_div;
		}

		FD922_VideoOutOddDataCount_W(dev->csr, io->index - 2, oddC);
		FD922_VideoOutEvenDataCount_W(dev->csr, io->index - 2, evenC);

		if (io->format.pixelformat == V4L2_PIX_FMT_V210) {
			io->format_conv = FORWARD_V4L2_CONV_10BIT_V210;
		} else {
			io->format_conv = FORWARD_V4L2_CONV_NONE;
		}

		FD922_VideoOutCS_W(dev->csr, io->index - 2, ocs);
	}
}

static void fd922_start(struct forward_v4l2_io *io)
{
	struct forward_dev *dev = io->v4l2->dev;
	dev->cfg.toggle_streaming(dev, io->index, true);
}

static void fd922_stop(struct forward_v4l2_io *io)
{
	struct forward_dev *dev = io->v4l2->dev;

	if (io->state == FORWARD_IO_TX) {
		FD922_VideoOutCS ocs = FD922_VideoOutCS_R(dev->csr, io->index - 2);
		ocs.atcEnable = 0;
		FD922_VideoOutCS_W(dev->csr, io->index - 2, ocs);
	}

	dev->cfg.toggle_streaming(dev, io->index, false);
}

static bool fd922_signal_present(const struct forward_v4l2_io *io)
{
	struct forward_dev *dev = io->v4l2->dev;
	FD922_VideoInCS ics = FD922_VideoInCS_R(dev->csr, io->index);
	return ics.cd ? true : false;
}

static u32 fd922_io_timestamp(const struct forward_v4l2_io *io)
{
	struct forward_dev *dev = io->v4l2->dev;
	u32 ts;
	if (io->state == FORWARD_IO_RX)
		ts = FD922_VideoInIRQRefTime_R(dev->csr, io->index).time;
	else if (io->sync)
		ts = FD922_SyncGeneratorTime_R(dev->csr, io->sync_gen).time;
	else
		ts = FD922_VideoClockCounter_R(dev->csr).counter;
	return ts;
}

static u32 fd922_hw_timestamp(struct forward_v4l2 *v4l2)
{
	struct forward_dev *dev = v4l2->dev;
	return FD922_VideoClockCounter_R(dev->csr).counter;
}

static s64 fd922_clock_deviation(struct forward_v4l2 *v4l2)
{
	// TODO: Read clock deviation
	return 0;
}

static s64 fd922_set_clock_deviation(struct forward_v4l2 *v4l2, s64 ppt)
{
	return v4l2->dev->pll->ops->tune(v4l2->dev->pll, ppt);
}

static int fd922_genlock_query(const struct forward_v4l2_genlock *gl,
			       enum v4l2_forward_genlock_source src, struct forward_v4l2_timings *t)
{
	struct forward_v4l2 *v4l2 = gl->v4l2;

	if (src == V4L_FORWARD_GENLOCK_SRC_MASTER) {
		*t = v4l2->io[2].timings;
		return 0;
	} else if (src == V4L_FORWARD_GENLOCK_SRC_IN0)
		return fd922_timings_query(&v4l2->io[0], t);
	else if (src == V4L_FORWARD_GENLOCK_SRC_IN1)
		return fd922_timings_query(&v4l2->io[1], t);
	else if (src == V4L_FORWARD_GENLOCK_SRC_ANALOG) {
		FD922_AnalogSync cnt = FD922_AnalogSync_R(v4l2->dev->csr);

		if ((cnt.hsync > 2048) || (cnt.vsync > 1000000) || (cnt.hsync == 0) ||
		    (cnt.vsync == 0))
			return -ENOLINK;

		return forward_v4l2_timing_guess_analog(18000000 / cnt.hsync,
							18000000000 / cnt.vsync, t) ?
			       0 :
			       -ERANGE;
	} else
		return -EINVAL;
}

static int fd922_genlock_switch(const struct forward_v4l2_genlock *gl,
				enum v4l2_forward_genlock_source src,
				struct forward_v4l2_timings *t)
{
	struct forward_v4l2 *v4l2 = gl->v4l2;
	struct forward_pll *pll = v4l2->dev->pll;
	FD922_SyncGenerator sg = { 0 };
	FD922_SyncGeneratorField sgf = { 0 };
	struct forward_pll_mode *mode = NULL;

	if (src == V4L_FORWARD_GENLOCK_SRC_MASTER) {
		mode = pll->ops->find_mode(pll, 0, 0, false, 148500000, false);
		pll->ops->switch_mode(pll, mode);
		pll->ops->select_input(pll, 0, true, false);
		pll->ops->calibrate(pll);
	} else {
		unsigned int pll_pix_freq = 0;
		unsigned int pll_h_freq = 0;
		int pll_input = 0;

		FD922_Genlock gl = FD922_Genlock_R(v4l2->dev->csr);

		pll_pix_freq = forward_v4l2_pixel_clock(t);
		pll_h_freq = pll_pix_freq / t->sdi.width;
		if (t->sdi.mode == FORWARD_SDI_MODE_6G_SDI) {
			pll_pix_freq /= 2;
			pll_h_freq /= 2;
		} else if (t->sdi.mode == FORWARD_SDI_MODE_12G_SDI) {
			pll_pix_freq /= 4;
			pll_h_freq /= 2;
		}
		if (src == V4L_FORWARD_GENLOCK_SRC_ANALOG) {
			pll_input = 0;
			gl.mode = (t->sdi.mode == FORWARD_SDI_MODE_SD_SDI) ? 1 : 0;
		} else {
			pll_input = (src == V4L_FORWARD_GENLOCK_SRC_IN0) ? 1 : 2;
			gl.mode = (src == V4L_FORWARD_GENLOCK_SRC_IN0) ? 2 : 3;
		}

		gl.m = t->sdi.m ? true : false;

		mode = pll->ops->find_mode(pll, pll_pix_freq, pll_h_freq, t->sdi.m, 148500000,
					   false);
		if (!mode)
			return -EINVAL;

		FD922_Genlock_W(v4l2->dev->csr, gl);
		pll->ops->switch_mode(pll, mode);

		pll->ops->select_input(pll, pll_input, false, false);
		pll->ops->calibrate(pll);
	}

	sg.frameSize = t->sdi.width * t->sdi.height;
	if (t->sdi.mode == FORWARD_SDI_MODE_SD_SDI)
		sg.frameSize *= 11;
	else if (t->sdi.mode == FORWARD_SDI_MODE_HD_SDI)
		sg.frameSize *= 2;
	if (t->sdi.interlaced)
		sgf.fieldSize = sg.frameSize / 2;
	else
		sgf.fieldSize = sg.frameSize;

	if (!sg.frameSize || !sgf.fieldSize) {
		sg.frameSize = 5940000;
		sgf.fieldSize = 2970000;
	}
	sg.m = t->sdi.m ? 1 : 0;

	FD922_SyncGenerator_W(v4l2->dev->csr, 0, sg);
	FD922_SyncGeneratorField_W(v4l2->dev->csr, 0, sgf);

	return 0;
}

static u32 fd922_genlock_sync_irq_mask(const struct forward_v4l2_genlock *gl, int num)
{
	return num ? (1 << 14) : (1 << 12);
}

static bool fd922_genlock_sync_irq_info(const struct forward_v4l2_genlock *gl, int num, u32 irq,
					bool *field, u32 *gl_time, s32 *gl_offset)
{
	struct forward_v4l2 *v4l2 = gl->v4l2;
	u32 ref_time = 0, sync_time;

	if (!(irq & (num ? (1 << 14) : (1 << 12))))
		return false;

	*field = (irq & (num ? (1 << 15) : (1 << 13))) ? true : false;
	sync_time = FD922_SyncGeneratorTime_R(v4l2->dev->csr, num).time;

	if (gl->source == V4L_FORWARD_GENLOCK_SRC_IN0)
		ref_time = FD922_VideoInIRQRefTime_R(v4l2->dev->csr, 0).time;
	else if (gl->source == V4L_FORWARD_GENLOCK_SRC_IN1)
		ref_time = FD922_VideoInIRQRefTime_R(v4l2->dev->csr, 1).time;
	else if (gl->source == V4L_FORWARD_GENLOCK_SRC_ANALOG)
		ref_time = FD922_AnalogVSyncTime_R(v4l2->dev->csr).time;

	*gl_time = sync_time;
	*gl_offset = (s32)(sync_time - ref_time);
	if (gl->timings.sdi.m)
		*gl_offset = (s64)*gl_offset * 1000LL / 1001LL;

	return true;
}

static void fd922_genlock_sync_offset(const struct forward_v4l2_genlock *gl, int num, s32 offset,
				      bool comp_delay)
{
	s32 frame_size = gl->timings.sdi.height * gl->timings.sdi.width;
	FD922_SyncGeneratorPhase ph;
	s32 sync_gen_offset = 0;

	if (gl->source == V4L_FORWARD_GENLOCK_SRC_ANALOG) {
		if (FD922_FIRMWARE_REVISION(gl->v4l2->dev) < FD922_GENLOCK_0H_REVISION) {
			if (gl->timings.sdi.height == 1125)
				sync_gen_offset = -(gl->timings.sdi.width -
						    gl->timings.sdi.active_width - 192);
			else if (gl->timings.sdi.height == 750)
				sync_gen_offset = -(gl->timings.sdi.width -
						    gl->timings.sdi.active_width - 260);
			else if (gl->timings.sdi.height == 625)
				sync_gen_offset = -12 - 46;
			else if (gl->timings.sdi.height == 525)
				sync_gen_offset = -16 - 44;
		}

		sync_gen_offset += -gl->timings.sdi.width;
		if (!gl->timings.sdi.interlaced)
			sync_gen_offset += -gl->timings.sdi.width;
	}

	if (gl->timings.sdi.mode == FORWARD_SDI_MODE_SD_SDI) {
		frame_size *= 11;
		sync_gen_offset *= 11;
	} else if (gl->timings.sdi.mode == FORWARD_SDI_MODE_HD_SDI) {
		frame_size *= 2;
		sync_gen_offset *= 2;
	}

	if (gl->source == V4L_FORWARD_GENLOCK_SRC_ANALOG)
		sync_gen_offset += -FD922_GENLOCK_ANALOG_DELAY;
	else {
		if (gl->timings.sdi.mode == FORWARD_SDI_MODE_SD_SDI)
			sync_gen_offset += -FD922_GENLOCK_SD_DELAY;
		else if (gl->timings.sdi.mode == FORWARD_SDI_MODE_HD_SDI)
			sync_gen_offset += -FD922_GENLOCK_HD_DELAY;
		else
			sync_gen_offset += -FD922_GENLOCK_3G_DELAY;
	}

	if (!comp_delay)
		sync_gen_offset = 0;

	offset += sync_gen_offset;

	if (offset > frame_size)
		offset = offset % frame_size;
	if (offset < 0)
		offset = (offset % frame_size) + frame_size;

	ph.phase = offset;
	FD922_SyncGeneratorPhase_W(gl->v4l2->dev->csr, num, ph);
}

static void fd922_genlock_sync_output(const struct forward_v4l2_genlock *gl,
				      struct forward_v4l2_io *io)
{
	FD922_VideoOutCS cs = FD922_VideoOutCS_R(io->v4l2->dev->csr, (io->index - 2));
	bool correct = forward_v4l2_timings_fps_divisible(&gl->timings, &io->timings);
	cs.freeRunning = (io->sync && correct) ? 0 : 1;
	cs.syncSource = io->sync_gen;
	cs.clockM = gl->timings.sdi.m ? 1 : 0;
	FD922_VideoOutCS_W(io->v4l2->dev->csr, (io->index - 2), cs);
}

static int fd922_analog_rx_set_mode(struct forward_v4l2 *dev, enum v4l2_forward_analog_rx_mode mode)
{
	FD922_PPSLTCControl ctl;

	if (!FD922_IS_VERSION_4(dev->dev))
		return -EOPNOTSUPP;

	switch (mode) {
	case V4L_FORWARD_ANALOG_RX_MODE_PPS:
		ctl.mode = 1;
		break;
	case V4L_FORWARD_ANALOG_RX_MODE_LTC:
		ctl.mode = 2;
		break;
	default:
		ctl.mode = 0;
		break;
	}
	FD922_PPSLTCControl_W(dev->dev->csr, ctl);

	return 0;
}

static enum v4l2_forward_analog_rx_mode fd922_analog_rx_get_mode(struct forward_v4l2 *dev)
{
	FD922_PPSLTCControl ctl;

	if (!FD922_IS_VERSION_4(dev->dev))
		return V4L_FORWARD_ANALOG_RX_MODE_GENLOCK;

	ctl = FD922_PPSLTCControl_R(dev->dev->csr);
	switch (ctl.mode) {
	case 1:
		return V4L_FORWARD_ANALOG_RX_MODE_PPS;
		break;
	case 2:
		return V4L_FORWARD_ANALOG_RX_MODE_LTC;
		break;
	default:
		return V4L_FORWARD_ANALOG_RX_MODE_GENLOCK;
		break;
	}
	return V4L_FORWARD_ANALOG_RX_MODE_GENLOCK;
}

static int fd922_analog_rx_timestamp(struct forward_v4l2 *dev, bool *valid, u32 *timestamp,
				     u32 *cur_time, u64 *sys_time)
{
	FD922_PPSLTCTimestamp ts;
	FD922_VideoClockCounter clk;
	FD922_PPSLTCControl ctl;

	if (!FD922_IS_VERSION_4(dev->dev))
		return -EOPNOTSUPP;

	ctl = FD922_PPSLTCControl_R(dev->dev->csr);
	ts = FD922_PPSLTCTimestamp_R(dev->dev->csr);
	*sys_time = ktime_get_ns();
	clk = FD922_VideoClockCounter_R(dev->dev->csr);

	*valid = ctl.valid;
	*timestamp = ts.time;
	*cur_time = clk.counter;

	return 0;
}

static int fd922_analog_rx_timecode(struct forward_v4l2 *dev, bool *valid, struct v4l2_timecode *tc)
{
	FD922_PPSLTCControl ctl;
	FD922_PPSLTCDataHigh dh;
	FD922_PPSLTCDataLow dl;
	FD922_AnalogSync s;
	u64 data;

	if (!FD922_IS_VERSION_4(dev->dev))
		return -EOPNOTSUPP;

	ctl = FD922_PPSLTCControl_R(dev->dev->csr);

	*valid = ctl.valid ? true : false;

	if (ctl.mode == 0) {
		s = FD922_AnalogSync_R(dev->dev->csr);
		if ((s.hsync > 1024) || (s.vsync > 1000000) || (s.hsync == 0) || (s.vsync == 0))
			*valid = false;
	}

	if (!*valid)
		return 0;

	dh = FD922_PPSLTCDataHigh_R(dev->dev->csr);
	dl = FD922_PPSLTCDataLow_R(dev->dev->csr);
	data = ((u64)dh.data << 32) | (dl.data);

	tc->type = 0; // FIXME: detect framerate

	if (ctl.mode == 0) {
		int fps100 = 1350000000 / s.vsync;
		if (s.vsync / s.hsync < 700)
			fps100 /= 2;

		if (fps100 < 2450)
			tc->type = V4L2_TC_TYPE_24FPS;
		else if (fps100 < 2750)
			tc->type = V4L2_TC_TYPE_25FPS;
		else if (fps100 < 3250)
			tc->type = V4L2_TC_TYPE_30FPS;
		else if (fps100 < 5500)
			tc->type = V4L2_TC_TYPE_50FPS;
		else if (fps100 < 6500)
			tc->type = V4L2_TC_TYPE_50FPS;
	}

	tc->flags = ((data & (1 << 14)) ? V4L2_TC_FLAG_DROPFRAME : 0) |
		    ((data & (1 << 15)) ? V4L2_TC_FLAG_COLORFRAME : 0);
	tc->flags |= (((data >> 27) & 0x1) << 2) | (((data >> 58) & 0x1) << 3) |
		     (((data >> 43) & 0x1) << 4);
	tc->frames = ((data >> 0) & 0xF) + ((data >> 8) & 0x3) * 10;
	tc->seconds = ((data >> 16) & 0xF) + ((data >> 24) & 0x7) * 10;
	tc->minutes = ((data >> 32) & 0xF) + ((data >> 40) & 0x7) * 10;
	tc->hours = ((data >> 48) & 0xF) + ((data >> 56) & 0x3) * 10;
	tc->userbits[0] = ((data >> 4) & 0x0F) | ((data >> 8) & 0xF0);
	tc->userbits[1] = ((data >> 20) & 0x0F) | ((data >> 24) & 0xF0);
	tc->userbits[2] = ((data >> 36) & 0x0F) | ((data >> 40) & 0xF0);
	tc->userbits[3] = ((data >> 52) & 0x0F) | ((data >> 56) & 0xF0);

	return 0;
}

static int fd922_toggle_clone(const struct forward_v4l2_io *io, bool clone)
{
	int out = io->index - 2;
	struct forward_v4l2_io *master = NULL;
	FD922_VideoOutCS ocs;

	if (!(out & 0x1))
		return -ENOTSUPP;

	master = &io->v4l2->io[io->index - 1];

	if (clone)
		fd922_timings_set(io, &master->timings);
	else
		fd922_timings_set(io, &io->timings);

	ocs = FD922_VideoOutCS_R(io->v4l2->dev->csr, out);
	ocs.clone = clone ? 1 : 0;
	FD922_VideoOutCS_W(io->v4l2->dev->csr, out, ocs);

	return 0;
}

struct forward_v4l2_dev_ops fd922_ops = {
	.irq_mask = fd922_irq_mask,
	.irq_info = fd922_irq_info,
	.region_info = fd922_region_info,
	.buffer_info = fd922_buffer_info,
	.timings_valid = fd922_timings_valid,
	.timings_set = fd922_timings_set,
	.timings_query = fd922_timings_query,
	.timings_changed = fd922_timings_changed,
	.check_fmt = fd922_check_fmt,
	.enum_fmt = fd922_enum_fmt,
	.update_meta = fd922_update_meta,
	.update_timecode = fd922_update_timecode,
	.prepare = fd922_prepare,
	.start = fd922_start,
	.stop = fd922_stop,
	.signal_present = fd922_signal_present,
	.hw_buffers = FD922_NUMBER_IN_BUFFERS,
	.vbi_support = true,

	.io_timestamp = fd922_io_timestamp,
	.hw_timestamp = fd922_hw_timestamp,
	.clock_deviation = fd922_clock_deviation,
	.set_clock_deviation = fd922_set_clock_deviation,

	.genlock_query = fd922_genlock_query,
	.genlock_switch = fd922_genlock_switch,
	.genlock_sync_irq_mask = fd922_genlock_sync_irq_mask,
	.genlock_sync_irq_info = fd922_genlock_sync_irq_info,
	.genlock_sync_offset = fd922_genlock_sync_offset,
	.genlock_sync_output = fd922_genlock_sync_output,

	.toggle_clone = fd922_toggle_clone,

	.analog_rx_set_mode = fd922_analog_rx_set_mode,
	.analog_rx_get_mode = fd922_analog_rx_get_mode,
	.analog_rx_timestamp = fd922_analog_rx_timestamp,
	.analog_rx_timecode = fd922_analog_rx_timecode,
};
