/*
   forward-v4l2-fd788.c - v4l2 driver for SoftLab-NSK FD788 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 "forward-v4l2-genlock.h"

#include "forward-pll.h"

#include <linux/delay.h>

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

#define FD788_BUFFER_NUMBER(in, irq) (((irq) >> ((in)*3 + 1)) & 0x3)
#define FD788_BUFFER_NEXT(buf, offset, count) ((((unsigned int)(buf)) + (offset)) % (count))
#define FD788_NUMBER_IN_BUFFERS (4)
#define FD788_NUMBER_OUT_BUFFERS (2)

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

#define FD788_GENLOCK_ANALOG_DELAY 306
#define FD788_GENLOCK_SD_NTSC_DELAY 27981
#define FD788_GENLOCK_SD_PAL_DELAY 364
#define FD788_GENLOCK_HD_DELAY 193
#define FD788_GENLOCK_3G_DELAY 101

#define FD788_YC_SWAP_REVISION 849
#define FD788_ASI_PERIOD_REVISION 873

#define FD788_IS_VERSION_2(ver) (((ver >> 24) & 0xF) >= 2)

static void fd788_check_pll_configuration(struct forward_v4l2 *v4l2)
{
	FD788_ClockFrequency freq;
	bool clk_valid;
	bool clk_m;
	int i;
	bool any_m = false;
	struct forward_pll *pll = v4l2->dev->pll;

	if ((v4l2->genlock->source != V4L_FORWARD_GENLOCK_SRC_MASTER) ||
	    (v4l2->genlock->new_source != V4L_FORWARD_GENLOCK_SRC_MASTER))
		return;

	if (!pll)
		return;

	mutex_lock(&v4l2->dev->lock);

	freq = FD788_ClockFrequency_R(v4l2->dev->csr);
	clk_valid = (freq.frequency > 148200) && (freq.frequency < 148800);
	clk_m = (148500 - freq.frequency) > 75;

	for (i = 0; i < 8; i++) {
		struct forward_v4l2_timings *t = &v4l2->io[i].timings;

		if (v4l2->io[i].state != FORWARD_IO_TX)
			continue;

		if (!clk_valid)
			clk_m = v4l2->io[i].timings.sdi.m;

		if (t->sdi.m) {
			any_m = true;
			break;
		}

		if ((i == 4) && t->sdi.mode == FORWARD_SDI_MODE_QUAD_3G_SDI)
			i += 3;
		if (((i == 4) || (i == 6)) && t->sdi.mode == FORWARD_SDI_MODE_DUAL_3G_SDI)
			i += 1;
	}

	if ((clk_m != any_m) || !clk_valid) {
		struct forward_pll_mode *mode;
		mode = pll->ops->find_mode(pll, 0, 0, any_m, 148500000, any_m);

		if (!mode)
			return;

		pll->ops->switch_mode(pll, mode);
		pll->ops->calibrate(pll);

		for (i = 0; i < 8; i++) {
			FD788_VideoOutCS ocs;
			if (v4l2->io[i].state != FORWARD_IO_TX)
				continue;

			ocs = FD788_VideoOutCS_R(v4l2->dev->csr, i);
			ocs.clockM = clk_m ? 1 : 0;
			FD788_VideoOutCS_W(v4l2->dev->csr, i, ocs);
		}
	}

	mutex_unlock(&v4l2->dev->lock);
}

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

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

	*cur_buf = FD788_BUFFER_NUMBER(io->index, irq);
	*prev_buf = FD788_BUFFER_NEXT(*cur_buf, -1, FD788_NUMBER_IN_BUFFERS);
	*next_buf = FD788_BUFFER_NEXT(*cur_buf, 1, FD788_NUMBER_IN_BUFFERS);

	return true;
}

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

	*cur_buf = FD788_BUFFER_NUMBER(io->index, irq);
	*prev_buf = FD788_BUFFER_NEXT(*cur_buf, -1, FD788_NUMBER_OUT_BUFFERS);
	*next_buf = FD788_BUFFER_NEXT(*cur_buf, 1, FD788_NUMBER_OUT_BUFFERS);

	return true;
}

inline bool fd788_irq_info(struct forward_v4l2_io *io, u32 irq, int *cur_buf, int *prev_buf,
			   int *next_buf)
{
	if (io->state == FORWARD_IO_TX)
		return fd788_irq_info_out(io, irq, cur_buf, prev_buf, next_buf);
	else
		return fd788_irq_info_in(io, irq, cur_buf, prev_buf, next_buf);
}

static void fd788_region_info(const struct forward_v4l2_io *io, u32 *address, size_t *size)
{
	if (io->timings.sdi.mode == FORWARD_SDI_MODE_QUAD_3G_SDI) {
		*size = 0x10000000UL;
	} else if (io->timings.sdi.mode == FORWARD_SDI_MODE_DUAL_3G_SDI) {
		*size = 0x08000000UL;
	} else {
		*size = 0x04000000UL;
	}
	*address = io->index * 0x08000000UL;
}

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

	if (io->timings.sdi.mode == FORWARD_SDI_MODE_QUAD_3G_SDI) {
		*size = 0x04000000UL;
	} else if (io->timings.sdi.mode == FORWARD_SDI_MODE_DUAL_3G_SDI) {
		*size = 0x02000000UL;
	} else {
		*size = 0x01000000UL;
	}
	*address = num * 0x08000000UL + buffer * *size;
}

static bool fd788_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_QUAD_3G_SDI) && (io->state == FORWARD_IO_RX) &&
	    (io->index != 0))
		return false;

	if ((t->sdi.mode == FORWARD_SDI_MODE_QUAD_3G_SDI) && (io->state == FORWARD_IO_TX) &&
	    (io->index != 4))
		return false;

	if ((t->sdi.mode == FORWARD_SDI_MODE_DUAL_3G_SDI) && (io->state == FORWARD_IO_RX) &&
	    (io->index != 0) && (io->index != 2))
		return false;

	if ((t->sdi.mode == FORWARD_SDI_MODE_DUAL_3G_SDI) && (io->state == FORWARD_IO_TX) &&
	    (io->index != 4) && (io->index != 6))
		return false;

	if ((t->sdi.mode != FORWARD_SDI_MODE_QUAD_3G_SDI) &&
	    (t->sdi.mode != FORWARD_SDI_MODE_DUAL_3G_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 fd788_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;
	FD788_VideoInCS ics = FD788_VideoInCS_R(dev->csr, io->index);
	FD788_VideoInLine line;
	FD788_VideoInPixel pixel;
	FD788_VideoInVPID vpid = FD788_VideoInVPID_R(dev->csr, io->index);
	FD788_VideoInFrameSize framesize = FD788_VideoInFrameSize_R(dev->csr, io->index);
	u32 pixel_clk;
	int width, height, active_width, active_height;
	u8 vpid_mode = (vpid.vpid >> 24) & 0xFF;
	bool dq = (vpid_mode == 0x96) || (vpid_mode == 0x97);
	bool quad = (vpid_mode == 0x97);
	bool result;

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

	if (!ics.cd)
		return -ENOLINK;

	// Hack
	if (!ics.formatLocked && ics.dqEnable) {
		ics.dqEnable = 0;
		FD788_VideoInCS_W(dev->csr, io->index, ics);
		return -ENOLCK;
	}
	if (dq && ics.dqEnable && (quad ? !ics.dqQuad : ics.dqQuad)) {
		ics.dqQuad = quad ? 1 : 0;
		FD788_VideoInCS_W(dev->csr, io->index, ics);
		return -ENOLCK;
	}

	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_SD_SDI;
		break;
	case 2:
		mode = FORWARD_SDI_MODE_3G_SDI;
		break;
	default:
		mode = FORWARD_SDI_MODE_INVALID;
		break;
	}

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

	line = FD788_VideoInLine_R(dev->csr, io->index);
	pixel = FD788_VideoInPixel_R(dev->csr, io->index);
	vpid = FD788_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 (dq && quad && (io->index == 0))
		mode = FORWARD_SDI_MODE_QUAD_3G_SDI;
	else if (dq && ((io->index == 0) || (io->index == 2)))
		mode = FORWARD_SDI_MODE_DUAL_3G_SDI;

	if ((mode == FORWARD_SDI_MODE_QUAD_3G_SDI) || (mode == FORWARD_SDI_MODE_DUAL_3G_SDI)) {
		width *= 2;
		height *= 2;
		active_width *= 2;
		active_height *= 2;
	}

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

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

	//Hack
	if (!result && !dq && ics.dqEnable) {
		ics.dqEnable = 0;
		FD788_VideoInCS_W(dev->csr, io->index, ics);
	}

	return result ? 0 : -ERANGE;
}

inline int fd788_timings_set_in(const struct forward_v4l2_io *io,
				const struct forward_v4l2_timings *t)
{
	struct forward_dev *dev = io->v4l2->dev;
	FD788_VideoInCS ics = FD788_VideoInCS_R(dev->csr, io->index);
	u32 map, newmap;
	int i;
	bool dq, quad, second;

	if (!fd788_timings_valid(io, t))
		return -ERANGE;

	quad = io->timings.sdi.mode == FORWARD_SDI_MODE_QUAD_3G_SDI;
	dq = (t->sdi.mode == FORWARD_SDI_MODE_QUAD_3G_SDI) ||
	     (t->sdi.mode == FORWARD_SDI_MODE_DUAL_3G_SDI);

	if (dq && (io->index != 0) && (io->index != 2))
		return -EINVAL;

	if (quad && (io->index != 0))
		return -EINVAL;

	ics.dqEnable = dq ? 1 : 0;
	ics.dqQuad = quad ? 1 : 0;
	FD788_VideoInCS_W(dev->csr, io->index, ics);

	if (!dq)
		return 0;

	second = io->index == 2;

	map = ioread32(&dev->csr[FD788_Video4KSwitch_A]);
	newmap = map;

	for (i = io->index; i < io->index + (ics.dqQuad ? 4 : 2); i++) {
		FD788_VideoInVPID vpid = FD788_VideoInVPID_R(dev->csr, i);
		int link = ((vpid.vpid >> 6) & 0x3) >> (quad ? 0 : 1);

		newmap &= ~(3 << (link * 2 + (second ? 8 : 0)));
		newmap |= ((i - io->index) << (link * 2 + (second ? 8 : 0)));
	}

	iowrite32(newmap, &dev->csr[FD788_Video4KSwitch_A]);

	return 0;
}

static void fd788_configure_quad_out(const struct forward_v4l2_io *io)
{
	struct forward_dev *dev = io->v4l2->dev;
	u32 map, newmap;
	int i;
	bool quad;
	bool second;

	if (io->timings.sdi.mode != FORWARD_SDI_MODE_DUAL_3G_SDI &&
	    io->timings.sdi.mode != FORWARD_SDI_MODE_QUAD_3G_SDI)
		return;

	if ((io->index != 4) && (io->index != 6))
		return;

	if ((io->index != 4) && (io->timings.sdi.mode == FORWARD_SDI_MODE_QUAD_3G_SDI))
		return;

	quad = io->timings.sdi.mode == FORWARD_SDI_MODE_QUAD_3G_SDI;
	second = io->index == 6;
	map = ioread32(&dev->csr[FD788_Video4KSwitch_A]);
	newmap = map;

	for (i = io->index; i < io->index + (quad ? 4 : 2); i++) {
		int link = (i - io->index);

		newmap &= ~(3 << (link * 2 + (second ? 24 : 16)));
		newmap |= (link << (link * 2 + (second ? 24 : 16)));
	}

	if (newmap != map)
		iowrite32(newmap, &dev->csr[FD788_Video4KSwitch_A]);

	for (i = io->index; i < io->index + (quad ? 4 : 2); i++) {
		FD788_VideoOutCS ocs = FD788_VideoOutCS_R(dev->csr, i);

		if (ocs.mode != 2) {
			ocs.reset = 1;
			FD788_VideoOutCS_W(dev->csr, i, ocs);

			*((u32 *)&ocs) = 0;

			ocs.reset = 0;
			ocs.levelB = 0;
			ocs.freeRunning = 1;
			ocs.mode = 2;
			ocs.clone = 0;
			ocs.playback = 0;
			FD788_VideoOutCS_W(dev->csr, i, ocs);
		}
	}
}

inline int fd788_timings_set_out(const struct forward_v4l2_io *io,
				 const struct forward_v4l2_timings *t)
{
	// TODO: ASI
	struct forward_dev *dev = io->v4l2->dev;
	FD788_VideoOutCS ocs = FD788_VideoOutCS_R(dev->csr, io->index);
	FD788_VideoOutLine line;
	FD788_VideoOutPixel pixel;
	FD788_VideoOutStart start;
	FD788_VideoOutStop stop;
	FD788_VideoOutField field;
	FD788_VideoOutVPID vpid;
	bool dq;
	bool asi = t->sdi.flags & FORWARD_SDI_TIMINGS_F_ASI;
	int dq_div = 1;

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

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

	dq = !asi && ((t->sdi.mode == FORWARD_SDI_MODE_QUAD_3G_SDI) |
		      (t->sdi.mode == FORWARD_SDI_MODE_DUAL_3G_SDI));

	if (dq && (io->index != 4) && (io->index != 6))
		return -EINVAL;

	if ((t->sdi.mode == FORWARD_SDI_MODE_QUAD_3G_SDI) && (io->index != 4))
		return -EINVAL;

	ocs.levelB = 0;

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

	if (asi)
		ocs.mode = 3;

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

	ocs.dqEnable = 0;

	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);
	}

	FD788_VideoOutLine_W(dev->csr, io->index, line);
	FD788_VideoOutPixel_W(dev->csr, io->index, pixel);
	FD788_VideoOutStart_W(dev->csr, io->index, start);
	FD788_VideoOutStop_W(dev->csr, io->index, stop);
	FD788_VideoOutField_W(dev->csr, io->index, field);
	FD788_VideoOutVPID_W(dev->csr, io->index, vpid);
	FD788_VideoOutCS_W(dev->csr, io->index, ocs);

	if (asi) {
		ocs.playbackRaw = 1;
	} else {
		bool gl_correct =
			forward_v4l2_timings_fps_divisible(t, &io->v4l2->genlock->timings);
		fd788_configure_quad_out(io);
		ocs.dqQuad = (t->sdi.mode == FORWARD_SDI_MODE_QUAD_3G_SDI) ? 1 : 0;
		ocs.dqEnable = dq ? 1 : 0;
		ocs.freeRunning = (io->genlock && gl_correct) ? 0 : 1;
	}

	FD788_VideoOutCS_W(dev->csr, io->index, ocs);

	ocs.reset = 0;
	FD788_VideoOutCS_W(dev->csr, io->index, ocs);

	fd788_check_pll_configuration(io->v4l2);

	return 0;
}

static int fd788_timings_set(const struct forward_v4l2_io *io, const struct forward_v4l2_timings *t)
{
	if (io->state == FORWARD_IO_TX)
		return fd788_timings_set_out(io, t);
	else
		return fd788_timings_set_in(io, t);
}

static bool fd788_timings_changed(const struct forward_v4l2_io *io,
				  const struct forward_v4l2_timings *old,
				  struct forward_v4l2_timings *new)
{
	if (fd788_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 fd788_check_fmt(const struct forward_v4l2_io *io, struct v4l2_pix_format *f)
{
	struct forward_dev *dev = io->v4l2->dev;
	FD788_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 ((dev->fw_version & 0xFFFF) >= FD788_ASI_PERIOD_REVISION)
			f->sizeimage = FD788_ASI_IN_BUFFER_SIZE >> (io->asi_period);
		else
			f->sizeimage = FD788_ASI_IN_BUFFER_SIZE;
	} else if (f->pixelformat == V4L2_PIX_FMT_SL_RAW_ASI) {
		f->bytesperline = 0;
		if ((dev->fw_version & 0xFFFF) >= FD788_ASI_PERIOD_REVISION)
			f->sizeimage = FD788_ASI_IN_BUFFER_SIZE_RAW >> (io->asi_period);
		else
			f->sizeimage = FD788_ASI_IN_BUFFER_SIZE_RAW;
	}

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

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

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

	if (ics.mode == 3) {
		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 ((dev->fw_version & 0xFFFF) >= FD788_ASI_PERIOD_REVISION)
				f->sizeimage = FD788_ASI_IN_BUFFER_SIZE >> (io->asi_period);
			else
				f->sizeimage = FD788_ASI_IN_BUFFER_SIZE;
		} else if (f->pixelformat == V4L2_PIX_FMT_SL_RAW) {
			f->bytesperline = 0;
			if ((dev->fw_version & 0xFFFF) >= FD788_ASI_PERIOD_REVISION)
				f->sizeimage = FD788_ASI_IN_BUFFER_SIZE_RAW >> (io->asi_period);
			else
				f->sizeimage = FD788_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 fd788_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 fd788_update_meta(struct forward_v4l2_io *io)
{
	struct forward_dev *dev = io->v4l2->dev;

	if (io->state == FORWARD_IO_RX) {
		FD788_VideoInVPID vpid = FD788_VideoInVPID_R(dev->csr, io->index);

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

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

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

	return 0;
}

static void fd788_update_timecode(struct forward_v4l2_io *io, u64 timecode, u16 ddb)
{
	uint32_t *reg = io->v4l2->dev->csr + FD788_VideoOutATC_A(io->index);
	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 fd788_start(struct forward_v4l2_io *io)
{
	struct forward_dev *dev = io->v4l2->dev;

	if (io->state == FORWARD_IO_RX) {
		FD788_VideoInCS ics = FD788_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 ((dev->fw_version & 0xFFFF) >= FD788_ASI_PERIOD_REVISION)
			ics.asiPeriod = io->asi_period;
		ics.ycSwap = (io->format.pixelformat == V4L2_PIX_FMT_YUYV) ? 1 : 0;

		if ((io->format.pixelformat == V4L2_PIX_FMT_YUYV) &&
		    (dev->fw_version & 0xFFFF) < FD788_YC_SWAP_REVISION) {
			io->format_conv = FORWARD_V4L2_CONV_UYVY_YUYV;
		} else {
			io->format_conv = FORWARD_V4L2_CONV_NONE;
		}

		FD788_VideoInCS_W(dev->csr, io->index, ics);
	} else {
		FD788_VideoOutCS ocs = FD788_VideoOutCS_R(dev->csr, io->index);
		FD788_VideoOutOddDataCount oddC;
		FD788_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_QUAD_3G_SDI) |
				       (t->sdi.mode == FORWARD_SDI_MODE_DUAL_3G_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;
		}

		FD788_VideoOutOddDataCount_W(dev->csr, io->index, oddC);
		FD788_VideoOutEvenDataCount_W(dev->csr, io->index, 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;
		}

		FD788_VideoOutCS_W(dev->csr, io->index, ocs);
	}
	dev->cfg.toggle_streaming(dev, io->index, true);
}

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

	if (io->state == FORWARD_IO_TX) {
		FD788_VideoOutCS ocs = FD788_VideoOutCS_R(dev->csr, io->index);
		ocs.atcEnable = 0;
		FD788_VideoOutCS_W(dev->csr, io->index, ocs);
	}

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

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

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

u32 fd788_io_timestamp(const struct forward_v4l2_io *io)
{
	struct forward_dev *dev = io->v4l2->dev;
	u32 ts;
	if (io->state == FORWARD_IO_RX)
		ts = FD788_VideoInIRQRefTime_R(dev->csr, io->index).time;
	else
		ts = 0;
	return ts;
}

static s64 fd788_clock_deviation(struct forward_v4l2 *v4l2)
{
	struct forward_dev *dev = v4l2->dev;
	FD788_ClockDeviation dv = FD788_ClockDeviation_R(dev->csr);
	u64 deviation = dv.deviation * 1000000000000ULL / 2818578888ULL;

	return dv.direction ? -deviation : deviation;
}

static s64 fd788_set_clock_deviation(struct forward_v4l2 *v4l2, s64 ppt)
{
	struct forward_dev *dev = v4l2->dev;
	if (FD788_IS_VERSION_2(dev->fw_version)) {
		return v4l2->dev->pll->ops->tune(v4l2->dev->pll, ppt);
	} else {
		FD788_ClockDeviation dv;
		u64 value = ((ppt < 0) ? -ppt : ppt) * 2818572288ULL / 1000000000000ULL;

		dv.deviation = (value > 1290555) ? 1290555 : value;
		dv.direction = (ppt < 0) ? 1 : 0;

		FD788_ClockDeviation_W(dev->csr, dv);

		value = dv.deviation * 1000000000000ULL / 2818572288ULL;
		return (ppt < 0) ? -value : value;
	}
}

static int fd788_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) {
		int i;
		for (i = 0; i < 8; i++) {
			if (v4l2->io[0].state == FORWARD_IO_TX) {
				*t = v4l2->io[0].timings;
				break;
			}
		}
		return 0;
	} else if (src == V4L_FORWARD_GENLOCK_SRC_IN0)
		return (v4l2->io[0].state == FORWARD_IO_RX) ? fd788_timings_query(&v4l2->io[0], t) :
							      -EINVAL;
	else if (src == V4L_FORWARD_GENLOCK_SRC_IN1)
		return (v4l2->io[1].state == FORWARD_IO_RX) ? fd788_timings_query(&v4l2->io[1], t) :
							      -EINVAL;
	else if (src == V4L_FORWARD_GENLOCK_SRC_ANALOG) {
		FD788_AnalogSync cnt = FD788_AnalogSync_R(v4l2->dev->csr);

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

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

static int fd788_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;
	FD788_SyncGenerator sg = { 0 };
	FD788_SyncGeneratorField sgf = { 0 };
	struct forward_pll_mode *mode = NULL;
	bool v2 = FD788_IS_VERSION_2(v4l2->dev->fw_version);

	if (src == V4L_FORWARD_GENLOCK_SRC_MASTER) {
		mode = pll->ops->find_mode(pll, 0, 0, false, 148500000, false);
		pll->ops->switch_mode(pll, mode);
		if (v2)
			pll->ops->select_input(pll, 0, true, false);
		else
			pll->ops->select_input(pll, 1, false, false);
		pll->ops->calibrate(pll);
	} else {
		int pll_pix_freq = 0;
		int pll_h_freq = 0;
		FD788_Genlock gl = FD788_Genlock_R(v4l2->dev->csr);

		pll_h_freq = forward_v4l2_pixel_clock(t) / t->sdi.width;
		if (src == V4L_FORWARD_GENLOCK_SRC_ANALOG) {
			gl.mode = (t->sdi.mode == FORWARD_SDI_MODE_SD_SDI) ? 1 : 0;
		} else if ((src == V4L_FORWARD_GENLOCK_SRC_IN0) ||
			   (src == V4L_FORWARD_GENLOCK_SRC_IN1)) {
			if ((t->sdi.mode == FORWARD_SDI_MODE_SD_SDI) ||
			    (t->sdi.flags & FORWARD_SDI_TIMINGS_F_ASI))
				pll_pix_freq = v2 ? 13500000 : 6750000;
			else if (t->sdi.mode == FORWARD_SDI_MODE_HD_SDI)
				pll_pix_freq = 74250000;
			else if (t->sdi.mode == FORWARD_SDI_MODE_3G_SDI)
				pll_pix_freq = 148500000;
			gl.mode = (src == V4L_FORWARD_GENLOCK_SRC_IN1) ? 3 : 2;
		}

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

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

		if (v2) {
			int input = 0;
			switch (src) {
			case V4L_FORWARD_GENLOCK_SRC_ANALOG:
				input = 0;
				break;
			case V4L_FORWARD_GENLOCK_SRC_IN0:
				input = 1;
				break;
			case V4L_FORWARD_GENLOCK_SRC_IN1:
				input = 2;
				break;
			default:
				input = 0;
				break;
			}
			pll->ops->select_input(pll, input, false, false);
			pll->ops->calibrate(pll);
		} else {
			if ((src == V4L_FORWARD_GENLOCK_SRC_IN0) ||
			    (src == V4L_FORWARD_GENLOCK_SRC_IN1)) {
				// Hack to avoid PLL->IN->PLL loop stuck
				FD788_VideoInCS cs;
				int count = 0;

				// Force Master clock
				pll->ops->select_input(pll, 1, false, false);
				pll->ops->calibrate(pll);

				// Wait for input
				do {
					msleep(100);
					cs = FD788_VideoInCS_R(
						v4l2->dev->csr,
						(src == V4L_FORWARD_GENLOCK_SRC_IN1) ? 1 : 0);
					count++;
				} while ((!cs.modeLocked) && (count < 100));

				// Switch to input clock
				pll->ops->select_input(pll, 0, false, true);
			} else {
				pll->ops->select_input(pll, 0, false, true);
				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;
	}

	FD788_SyncGenerator_W(v4l2->dev->csr, 0, sg);
	FD788_SyncGeneratorField_W(v4l2->dev->csr, 0, sgf);

	return 0;
}

u32 fd788_genlock_sync_irq_mask(const struct forward_v4l2_genlock *gl, int num)
{
	return 1 << (num * 2 + 24);
}

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

	if (!(irq & (1 << (num * 2 + 24))))
		return false;

	*field = (irq & (1 << (num * 2 + 25))) ? true : false;
	sync_time = FD788_SyncGeneratorTime_R(v4l2->dev->csr, num).time;

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

	*gl_offset = (s32)(sync_time - ref_time);

	return true;
}

void fd788_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;
	FD788_SyncGeneratorPhase ph;
	s32 sync_gen_offset = 0;

	if (gl->source == V4L_FORWARD_GENLOCK_SRC_ANALOG) {
		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 += -FD788_GENLOCK_ANALOG_DELAY;
	else {
		if (gl->timings.sdi.mode == FORWARD_SDI_MODE_SD_SDI)
			sync_gen_offset += (gl->timings.sdi.height == 525) ?
						   -FD788_GENLOCK_SD_NTSC_DELAY :
						   -FD788_GENLOCK_SD_PAL_DELAY;
		else if (gl->timings.sdi.mode == FORWARD_SDI_MODE_HD_SDI)
			sync_gen_offset += -FD788_GENLOCK_HD_DELAY;
		else
			sync_gen_offset += -FD788_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;
	FD788_SyncGeneratorPhase_W(gl->v4l2->dev->csr, num, ph);
}

void fd788_genlock_sync_output(const struct forward_v4l2_genlock *gl, struct forward_v4l2_io *io)
{
	FD788_VideoOutCS cs = FD788_VideoOutCS_R(io->v4l2->dev->csr, io->index);
	bool correct = forward_v4l2_timings_fps_divisible(&gl->timings, &io->timings);
	cs.freeRunning = (io->genlock && correct) ? 0 : 1;
	cs.syncSource = io->genlock_sync_gen;
	FD788_VideoOutCS_W(io->v4l2->dev->csr, io->index, cs);
}

struct forward_v4l2_dev_ops fd788_ops = {
	.irq_mask = fd788_irq_mask,
	.irq_info = fd788_irq_info,
	.region_info = fd788_region_info,
	.buffer_info = fd788_buffer_info,
	.timings_valid = fd788_timings_valid,
	.timings_set = fd788_timings_set,
	.timings_query = fd788_timings_query,
	.timings_changed = fd788_timings_changed,
	.check_fmt = fd788_check_fmt,
	.enum_fmt = fd788_enum_fmt,
	.update_meta = fd788_update_meta,
	.update_timecode = fd788_update_timecode,
	.start = fd788_start,
	.stop = fd788_stop,
	.signal_present = fd788_signal_present,
	.hw_buffers = FD788_NUMBER_IN_BUFFERS,
	.vbi_support = true,

	.io_timestamp = fd788_io_timestamp,
	.hw_timestamp = fd788_hw_timestamp,
	.clock_deviation = fd788_clock_deviation,
	.set_clock_deviation = fd788_set_clock_deviation,

	.genlock_query = fd788_genlock_query,
	.genlock_switch = fd788_genlock_switch,
	.genlock_sync_irq_mask = fd788_genlock_sync_irq_mask,
	.genlock_sync_irq_info = fd788_genlock_sync_irq_info,
	.genlock_sync_offset = fd788_genlock_sync_offset,
	.genlock_sync_output = fd788_genlock_sync_output,
};
