/*
   forward-passthrough-fd722.c - passthrough driver for FD722

   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-passthrough.h"
#include "forward-pll.h"
#include "fd722_reg.h"

#include <linux/types.h>
#include <linux/delay.h>

#define FD722_BUFFER_NUMBER_IN(in, irq) (((irq) >> ((in)*4 + 17)) & 0x7)
#define FD722_BUFFER_NUMBER_OUT(out, irq) (((irq) >> ((out)*2 + 5)) & 0x1)
#define FD722_BUFFER_NEXT(buf, offset, count) ((((unsigned int)(buf)) + (offset)) % (count))
#define FD722_NUMBER_IN_BUFFERS (8)
#define FD722_NUMBER_OUT_BUFFERS (2)

#define FD722_GENLOCK_ANALOG_DELAY 300
#define FD722_GENLOCK_SD_NTSC_DELAY 27953
#define FD722_GENLOCK_SD_PAL_DELAY 364
#define FD722_GENLOCK_HD_DELAY 189
#define FD722_GENLOCK_3G_DELAY 97

#define FD722_IS_VERSION_5(ver) (((ver >> 16) & 0xF) >= 6)

static u32 fd722_irq_mask(struct forward_passthrough_io *io)
{
	return (1 << (io->in_index * 4 + 16)) | (1 << ((io->out_index - 2) * 2 + 4));
}

static bool fd722_irq_info_in(struct forward_passthrough_io *io, u32 irq, int *prev, int *cur,
			      int *next, u32 *in_a_size)
{
	FD722_VideoInANCCounter anc;
	bool interlaced = io->format.interlaced;

	if (!(irq & (1 << (io->in_index * 4 + 16))))
		return false;

	*cur = FD722_BUFFER_NUMBER_IN(io->in_index, irq);
	*prev = FD722_BUFFER_NEXT(*cur, interlaced ? -1 : -2, FD722_NUMBER_IN_BUFFERS);
	*next = FD722_BUFFER_NEXT(*cur, interlaced ? 1 : 2, FD722_NUMBER_IN_BUFFERS);

	anc = FD722_VideoInANCCounter_R(io->pass->dev->csr, io->in_index);
	*in_a_size = anc.counter;

	if (!(*cur & 0x1))
		io->last_in_hw_timestamp =
			FD722_VideoInIRQRefTime_R(io->pass->dev->csr, (io->out_index - 2)).time;

	return true;
}

static bool fd722_irq_info_out(struct forward_passthrough_io *io, u32 irq, int *prev, int *cur,
			       int *next, u32 *audio_ptr)
{
	FD722_VideoOutAudio audio;

	if (!(irq & (1 << ((io->out_index - 2) * 2 + 4))))
		return false;

	*cur = FD722_BUFFER_NUMBER_OUT((io->out_index - 2), irq);
	*prev = FD722_BUFFER_NEXT(*cur, -1, FD722_NUMBER_OUT_BUFFERS);
	*next = FD722_BUFFER_NEXT(*cur, 1, FD722_NUMBER_OUT_BUFFERS);

	audio = FD722_VideoOutAudio_R(io->pass->dev->csr, (io->out_index - 2));

	*audio_ptr = audio.address / 4;

	return true;
}

static bool fd722_buffer_map_address(struct forward_passthrough_io *io, int buffer, bool out,
				     u32 *video, u32 *audio)
{
	if (!out & !io->format.interlaced & (buffer & 0x1))
		return false;

	if (out) {
		*video = (io->out_index - 2) * 0x01000000UL + buffer * 0x00800000UL + 0x05000000UL;
		*audio = (io->out_index - 2) * 0x00100000UL + 0x07000000UL;
	} else {
		*video = io->in_index * 0x02000000UL + (buffer * 0x00400000UL);
		*audio = io->in_index * 0x00800000UL + 0x04000000UL + (buffer * 0x00100000UL);
	}

	return true;
}

static void fd722_region_info(struct forward_passthrough_io *io, bool out, u32 *v_address,
			      size_t *v_size, u32 *a_address, size_t *a_size)
{
	if (out) {
		*v_size = 0x01000000UL;
		*v_address = (io->out_index - 2) * 0x01000000UL + 0x05000000UL;
		*a_size = 0x00100000UL;
		*a_address = (io->out_index - 2) * 0x00100000UL + 0x07000000UL;
	} else {
		*v_size = 0x02000000UL;
		*v_address = io->in_index * 0x02000000UL;
		*a_size = 0x00800000UL;
		*a_address = (io->out_index - 2) * 0x00800000UL + 0x04000000UL;
	}
}

static bool fd722_query_in_format(struct forward_passthrough_io *io,
				  struct forward_passthrough_format *fmt)
{
	struct forward_dev *dev = io->pass->dev;
	FD722_VideoInCS ics = FD722_VideoInCS_R(dev->csr, io->in_index);
	FD722_VideoInFrameSize framesize = FD722_VideoInFrameSize_R(dev->csr, io->in_index);
	FD722_VideoInLine line;
	FD722_VideoInPixel pixel;
	FD722_VideoInVPID vpid = FD722_VideoInVPID_R(dev->csr, io->in_index);
	u64 pixel_clk, real_pixel_clk;
	int width, height, active_width, active_height;

	if (!ics.cd)
		return false;

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

	switch (ics.mode) {
	case 0:
		pixel_clk = 74250000;
		break;
	case 1:
		pixel_clk = 13500000;
		break;
	case 2:
		pixel_clk = 148500000;
		break;
	default:
		pixel_clk = 0;
		break;
	}

	line = FD722_VideoInLine_R(dev->csr, io->in_index);
	pixel = FD722_VideoInPixel_R(dev->csr, io->in_index);
	if (!framesize.size)
		framesize.size = 1;
	width = pixel.totalPixels;
	height = line.totalLines;
	active_width = pixel.activePixels;
	active_height = line.activeLines * (ics.progressive ? 1 : 2);

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

	/*forward_info(
		dev,
		"quered format on IN%d: m = %llu, p = %d, w = %d, h = %d, aw = %d, ah = %d, clk = %llu\n",
		io->in_index, pixel_clk, ics.progressive, width, height, active_width,
		active_height, real_pixel_clk);*/

	if (!forward_passthrough_format_guess(pixel_clk, !ics.progressive, width, height,
					      active_width, active_height, real_pixel_clk, fmt))
		return false;

	fmt->vpid = vpid.vpid;
	fmt->size[0] =
		((fmt->height / (fmt->interlaced ? 2 : 1)) + fmt->topVBIs[0]) * fmt->width * 20 / 8;
	if (fmt->interlaced)
		fmt->size[1] = ((fmt->height / 2) + fmt->topVBIs[1]) * fmt->width * 20 / 8;
	else
		fmt->size[1] = fmt->size[0];

	return true;
}

static void fd722_setup_out_format(struct forward_passthrough_io *io,
				   struct forward_passthrough_format *fmt)
{
	struct forward_dev *dev = io->pass->dev;
	FD722_VideoOutCS ocs = FD722_VideoOutCS_R(dev->csr, (io->out_index - 2));
	FD722_VideoOutLine line;
	FD722_VideoOutPixel pixel;
	FD722_VideoOutStart start;
	FD722_VideoOutStop stop;
	FD722_VideoOutField field;
	FD722_VideoOutVPID vpid;

	ocs.reset = 1;
	ocs.levelB = 0;
	ocs.playbackRaw = 0;
	ocs.vbiTop = 1;
	ocs.vbiBottom = 0;
	ocs.vis10b = 1;
	ocs.vbi10b = 1;
	ocs.dataPresent = 1;
	ocs.atcEnable = 0;
	ocs.atcType = 0;
	ocs.clone = 0;
	ocs.test = 0;

	FD722_VideoOutCS_W(dev->csr, (io->out_index - 2), ocs);

	switch (fmt->pixelclock) {
	case 13500000:
		ocs.mode = 1;
		break;
	case 74250000:
		ocs.mode = 0;
		break;
	default:
		ocs.mode = 2;
		break;
	}

	ocs.progressive = fmt->interlaced ? 0 : 1;
	ocs.audioCount = io->a_out_groups - 1;

	line.totalLines = fmt->total_height;
	pixel.totalPixels = fmt->total_width;
	pixel.activePixels = fmt->width;
	start.startOdd = fmt->topVBIs[0] - 1;
	start.startEven = fmt->field_switch[1] + fmt->topVBIs[1];
	stop.stopOdd = start.startOdd + fmt->height / (fmt->interlaced ? 2 : 1);
	stop.stopEven = start.startEven + fmt->height / (fmt->interlaced ? 2 : 1);
	field.switchOdd = fmt->field_switch[0];
	field.switchEven = fmt->field_switch[1];
	vpid.vpid = fmt->vpid;

	FD722_VideoOutLine_W(dev->csr, (io->out_index - 2), line);
	FD722_VideoOutPixel_W(dev->csr, (io->out_index - 2), pixel);
	FD722_VideoOutStart_W(dev->csr, (io->out_index - 2), start);
	FD722_VideoOutStop_W(dev->csr, (io->out_index - 2), stop);
	FD722_VideoOutField_W(dev->csr, (io->out_index - 2), field);
	FD722_VideoOutVPID_W(dev->csr, (io->out_index - 2), vpid);
	FD722_VideoOutCS_W(dev->csr, (io->out_index - 2), ocs);

	ocs.reset = 0;
	FD722_VideoOutCS_W(dev->csr, (io->out_index - 2), ocs);
}

static void fd722_update_io_state(struct forward_passthrough_io *io)
{
	struct forward_dev *dev = io->pass->dev;
	FD722_VideoInCS ics = FD722_VideoInCS_R(dev->csr, io->in_index);

	io->no_input_signal = !ics.modeLocked || !ics.formatLocked;
}

static void fd722_configure_io(struct forward_passthrough_io *io)
{
	//if (!fd722_query_in_format(io, &io->format))
	//	return;

	// 576i50, see forward-passthrough-formats.c
	io->format = forward_passthrough_formats[FORWARD_FORMAT_SD_576I50];
	fd722_setup_out_format(io, &io->format);
	io->configured = true;
}

static void fd722_configure_genlock(struct forward_passthrough_io *io, bool analog)
{
	struct forward_passthrough *pass = io->pass;
	struct forward_pll *pll = pass->dev->pll;
	FD722_SyncGenerator sg = { 0 };
	FD722_SyncGeneratorField sgf = { 0 };
	struct forward_passthrough_format *fmt = &io->format;
	struct forward_pll_mode *mode;
	FD722_Genlock gl;
	int pll_in_h_freq = 0, pll_in_p_freq = 0;
	int i;
	bool v5 = FD722_IS_VERSION_5(io->pass->dev->fw_version);

	if (!io->configured)
		return;

	gl = FD722_Genlock_R(pass->dev->csr);

	if (analog) {
		pll_in_h_freq = fmt->pixelclock / fmt->total_width;
		gl.mode = 0;
	} else {
		if (io->format.pixelclock == 13500000)
			pll_in_p_freq = v5 ? 13500000 : 6750000;
		else
			pll_in_p_freq = (int)io->format.pixelclock;
		gl.mode = io->in_index ? 3 : 2;
	}

	mode = pll->ops->find_mode(pll, pll_in_p_freq, pll_in_h_freq, fmt->m, 148500000, fmt->m);
	if (!mode)
		return;

	FD722_Genlock_W(pass->dev->csr, gl);

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

	if (v5) {
		int input = 0;
		if (analog)
			input = 0;
		else
			input = io->in_index ? 2 : 1;
		pll->ops->select_input(pll, input, false, true);
		pll->ops->calibrate(pll);
	} else {
		if (!analog) {
			// Hack to avoid PLL->IN->PLL loop stuck
			FD722_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 = FD722_VideoInCS_R(io->pass->dev->csr, io->in_index ? 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 = fmt->total_width * fmt->total_height;
	if (fmt->pixelclock == 13500000)
		sg.frameSize *= 11;
	else if (fmt->pixelclock == 74250000)
		sg.frameSize *= 2;
	if (fmt->interlaced)
		sgf.fieldSize = sg.frameSize / 2;
	else
		sgf.fieldSize = sg.frameSize;

	if (!sg.frameSize || !sgf.fieldSize) {
		sg.frameSize = 5940000;
		sgf.fieldSize = 2970000;
	}

	FD722_SyncGenerator_W(pass->dev->csr, 0, sg);
	FD722_SyncGeneratorField_W(pass->dev->csr, 0, sgf);

	for (i = 0; i < pass->ops->num_io; i++) {
		struct forward_passthrough_io *o = &pass->io[i];
		FD722_VideoOutCS cs = FD722_VideoOutCS_R(pass->dev->csr, (o->out_index - 2));
		cs.syncSource = 0;
		cs.freeRunning = 0;
		FD722_VideoOutCS_W(pass->dev->csr, (o->out_index - 2), cs);
	}
}

static void fd722_genlock_offset(struct forward_passthrough_io *io, s32 offset)
{
	struct forward_passthrough *pass = io->pass;
	struct forward_passthrough_format *fmt = &io->format;

	s32 frame_size = fmt->total_height * fmt->total_width;
	FD722_SyncGeneratorPhase ph;
	s32 sync_gen_offset = 0;

	if (pass->analog_master) {
		if (fmt->total_height == 1125)
			sync_gen_offset = -(fmt->total_width - fmt->width - 192);
		else if (fmt->total_height == 750)
			sync_gen_offset = -(fmt->total_width - fmt->width - 260);
		else if (fmt->total_height == 625)
			sync_gen_offset = -12 - 46;
		else if (fmt->total_height == 525)
			sync_gen_offset = -16 - 44;

		sync_gen_offset += -fmt->total_width;
		if (!fmt->interlaced)
			sync_gen_offset += -fmt->total_width;
	}

	if (fmt->pixelclock == 13500000) {
		frame_size *= 11;
		sync_gen_offset *= 11;
	} else if (fmt->pixelclock == 74250000) {
		frame_size *= 2;
		sync_gen_offset *= 2;
	}

	if (pass->analog_master)
		sync_gen_offset += -FD722_GENLOCK_ANALOG_DELAY;
	else {
		if (fmt->pixelclock == 13500000)
			sync_gen_offset += (fmt->total_height == 525) ?
						   -FD722_GENLOCK_SD_NTSC_DELAY :
						   -FD722_GENLOCK_SD_PAL_DELAY;
		else if (fmt->pixelclock == 74250000)
			sync_gen_offset += -FD722_GENLOCK_HD_DELAY;
		else
			sync_gen_offset += -FD722_GENLOCK_3G_DELAY;
	}

	offset += sync_gen_offset;

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

	ph.phase = offset;
	FD722_SyncGeneratorPhase_W(pass->dev->csr, 0, ph);
}

u32 fd722_genlock_irq_mask(struct forward_passthrough_io *io)
{
	return 1 << 8;
}

bool fd722_genlock_irq_info(struct forward_passthrough_io *io, u32 irq, bool *field, s32 *gl_offset)
{
	struct forward_passthrough *pass = io->pass;
	u32 ref_time = 0, sync_time = 0;

	if (!(irq & (1 << 8)))
		return false;

	*field = (irq & (1 << 9)) ? true : false;
	sync_time = FD722_SyncGeneratorTime_R(pass->dev->csr, 0).time;

	if (pass->analog_master)
		ref_time = FD722_AnalogVSyncTime_R(pass->dev->csr).time;
	else
		ref_time = io->last_in_hw_timestamp;

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

	return true;
}

static void fd722_toggle_streaming(struct forward_passthrough_io *io, bool in_enable,
				   bool out_enable)
{
	if (in_enable) {
		FD722_VideoInCS ics = FD722_VideoInCS_R(io->pass->dev->csr, io->in_index);
		ics.vbiTop = 1;
		ics.vbiBottom = 0;
		ics.test = 0;
		ics.vbi10b = 1;
		ics.vis10b = 1;
		ics.captureRaw = io->raw ? 1 : 0;
		FD722_VideoInCS_W(io->pass->dev->csr, io->in_index, ics);
	}

	if (out_enable) {
		FD722_VideoOutOddDataCount oddC;
		FD722_VideoOutEvenDataCount evenC;
		FD722_VideoOutCS ocs = FD722_VideoOutCS_R(io->pass->dev->csr, io->out_index - 2);
		struct forward_passthrough_format *fmt = &io->format;

		ocs.vbiTop = 1;
		ocs.vbiBottom = 0;
		ocs.test = 0;
		ocs.vbi10b = 1;
		ocs.vis10b = 1;
		ocs.playbackRaw = io->raw ? 1 : 0;

		if (io->raw) {
			oddC.count = fmt->raw_size[0];
			evenC.count = fmt->raw_size[1];
		} else {
			oddC.count = fmt->size[0];
			evenC.count = fmt->size[1];
		}

		FD722_VideoOutOddDataCount_W(io->pass->dev->csr, (io->out_index - 2), oddC);
		FD722_VideoOutEvenDataCount_W(io->pass->dev->csr, (io->out_index - 2), evenC);
		FD722_VideoOutCS_W(io->pass->dev->csr, (io->out_index - 2), ocs);
	}

	io->pass->dev->cfg.toggle_streaming(io->pass->dev, io->in_index, in_enable);
	io->pass->dev->cfg.toggle_streaming(io->pass->dev, io->out_index, out_enable);
}

static void fd722_update_meta(struct forward_passthrough_io *io)
{
	FD722_VideoInVPID ivpid;
	FD722_VideoOutVPID ovpid;

	ivpid = FD722_VideoInVPID_R(io->pass->dev->csr, io->in_index);
	if (ivpid.vpid != 0) {
		ovpid.vpid = ivpid.vpid;
		FD722_VideoOutVPID_W(io->pass->dev->csr, (io->out_index - 2), ovpid);
	}
}

struct forward_passthrough_dev_ops fd722_ops = {
	.irq_mask = fd722_irq_mask,
	.irq_info_in = fd722_irq_info_in,
	.irq_info_out = fd722_irq_info_out,

	.region_info = fd722_region_info,
	.buffer_map_address = fd722_buffer_map_address,

	.update_io_state = fd722_update_io_state,
	.configure_io = fd722_configure_io,
	.configure_genlock = fd722_configure_genlock,
	.genlock_irq_mask = fd722_genlock_irq_mask,
	.genlock_irq_info = fd722_genlock_irq_info,
	.genlock_offset = fd722_genlock_offset,
	.toggle_streaming = fd722_toggle_streaming,

	.update_meta = fd722_update_meta,

	.num_io = 2,
	.num_in_buffers = 8,
	.num_out_buffers = 2,
	.video_buffer_size = 8 * 1024 * 1024,
	.audio_buffer_size = 1 * 1024 * 1024,
};
