/*
   forward-splicer-fd722.h - splicer driver for SoftLab-NSK FD722 video board

   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-splicer.h"

#include "fd722_reg.h"
#include "forward-pll.h"

#include <linux/delay.h>

#define FD722_IS_M2(dev) (dev->pci_dev->device == 0x0023)
#define FD722_IS_BYPASS(dev) (dev->pci_dev->device == 0x0024)
#define FD722_IS_VERSION_5(dev) \
	((((dev->fw_version >> 16) & 0xF) >= 6) || FD722_IS_M2(dev) || FD722_IS_BYPASS(dev))

#define FD722_INPUT_REG_INDEX(io) (io->in_index % 2)
#define FD722_OUTPUT_REG_INDEX(io) \
	(FD722_IS_M2(io->splicer->dev) ? (io->out_index - 1) : (io->out_index - 2))

#define FD722_INPUT_IRQ_NUM(io) ((io->in_index) * 4 + 16)

#define FD722_OUTPUT_IRQ_NUM_NORMAL(io) ((io->out_index - 2) * 2 + 4)
#define FD722_OUTPUT_IRQ_NUM_M2(io) ((io->out_index - 1) * 2 + 4)
#define FD722_OUTPUT_IRQ_NUM(io)                                       \
	(FD722_IS_M2(io->splicer->dev) ? FD722_OUTPUT_IRQ_NUM_M2(io) : \
					 FD722_OUTPUT_IRQ_NUM_NORMAL(io))

#define FD722_BUFFER_NEXT(buf, offset, count) ((((unsigned int)(buf)) + (offset)) % (count))
#define FD722_NUMBER_IN_BUFFERS (8)
#define FD722_NUMBER_OUT_BUFFERS (2)

// 50 buffers per second
#define FD722_ASI_BUFFER_SIZE (270000000ULL / 8 / 50)
#define FD722_ASI_BUFFER_SIZE_PAGE (((FD722_ASI_BUFFER_SIZE - 1) / PAGE_SIZE + 1) * PAGE_SIZE)

static forward_irq_t fd722_irq_mask(const struct forward_splicer_io *io)
{
	forward_irq_t result = { 0 };
	forward_irq_flags_set_one(result, FD722_INPUT_IRQ_NUM(io));
	forward_irq_flags_set_one(result, FD722_OUTPUT_IRQ_NUM(io));
	return result;
}

inline bool fd722_irq_info_in(struct forward_splicer_io *io, const forward_irq_t *irq,
			      int *in_buffer, size_t *size, u32 *timestamp)
{
	int buf;

	if (!forward_irq_has_irq(*irq, FD722_INPUT_IRQ_NUM(io)))
		return false;

	buf = forward_irq_extract_data(*irq, FD722_INPUT_IRQ_NUM(io) + 1, 3);
	buf = FD722_BUFFER_NEXT(buf, -1, FD722_NUMBER_IN_BUFFERS);

	*size = FD722_ASI_BUFFER_SIZE;
	*in_buffer = buf;
	*timestamp =
		FD722_VideoInIRQRefTime_R(io->splicer->dev->csr, FD722_INPUT_REG_INDEX(io)).time;

	return true;
}

inline bool fd722_irq_info_out(struct forward_splicer_io *io, const forward_irq_t *irq,
			       int *out_buffer, size_t *size, u32 *timestamp)
{
	int buf;
	if (!forward_irq_has_irq(*irq, FD722_OUTPUT_IRQ_NUM(io)))
		return false;

	buf = forward_irq_extract_data(*irq, FD722_OUTPUT_IRQ_NUM(io) + 1, 1);
	buf = FD722_BUFFER_NEXT(buf, +1, FD722_NUMBER_OUT_BUFFERS);

	*size = FD722_ASI_BUFFER_SIZE;
	*out_buffer = buf;
	*timestamp =
		FD722_VideoOutIRQRefTime_R(io->splicer->dev->csr, FD722_OUTPUT_REG_INDEX(io)).time;

	return true;
}

static bool fd722_irq_info(struct forward_splicer_io *io, const forward_irq_t *irq, bool *in,
			   int *in_buffer, size_t *in_size, u32 *in_timestamp, bool *out,
			   int *out_buffer, size_t *out_size, u32 *out_timestamp)
{
	*in = fd722_irq_info_in(io, irq, in_buffer, in_size, in_timestamp);
	*out = fd722_irq_info_out(io, irq, out_buffer, out_size, out_timestamp);
	return *in || *out;
}

static u32 fd722_buffer_map_address(const struct forward_splicer_io *io, int buffer, bool in)
{
	if (in)
		return FD722_INPUT_REG_INDEX(io) * 0x02000000UL + buffer * 0x00400000UL;
	else
		return FD722_OUTPUT_REG_INDEX(io) * 0x01000000UL + buffer * 0x00800000UL +
		       0x05000000UL;
}

static void fd722_configure_io(const struct forward_splicer_io *io)
{
	uint32_t *csr = io->splicer->dev->csr;
	int idx = FD722_INPUT_REG_INDEX(io), odx = FD722_OUTPUT_REG_INDEX(io);
	FD722_VideoOutCS ocs;
	FD722_VideoOutLine line;
	FD722_VideoOutPixel pixel;
	FD722_VideoOutStart start;
	FD722_VideoOutStop stop;
	FD722_VideoOutField field;
	FD722_VideoOutOddDataCount oddC;
	FD722_VideoOutEvenDataCount evenC;
	FD722_VideoInCS ics;

	memset(&ocs, 0, sizeof(ocs));
	ocs.reset = 1;
	FD722_VideoOutCS_W(csr, odx, ocs);

	ocs.mode = 3;
	ocs.playbackRaw = 1;
	ocs.dataPresent = 1;
	ocs.dpllEnable = 1;
	ocs.dpllSelect = idx;
	ocs.freeRunning = 1;
	FD722_VideoOutCS_W(csr, odx, ocs);

	line.totalLines = 540;
	FD722_VideoOutLine_W(csr, odx, line);

	pixel.activePixels = 720;
	pixel.totalPixels = 1000;
	FD722_VideoOutPixel_W(csr, odx, pixel);

	start.startOdd = 11;
	start.startEven = 281;
	FD722_VideoOutStart_W(csr, odx, start);

	stop.stopOdd = 267;
	stop.stopEven = 537;
	FD722_VideoOutStop_W(csr, odx, stop);

	field.switchOdd = 539;
	field.switchEven = 269;
	FD722_VideoOutField_W(csr, odx, field);

	oddC.count = 675000;
	FD722_VideoOutOddDataCount_W(csr, odx, oddC);

	evenC.count = 675000;
	FD722_VideoOutEvenDataCount_W(csr, odx, evenC);

	ocs.reset = 0;
	FD722_VideoOutCS_W(csr, odx, ocs);

	ics = FD722_VideoInCS_R(csr, idx);
	ics.captureRaw = 1;
	FD722_VideoInCS_W(csr, idx, ics);
}

static void fd722_io_state(const struct forward_splicer_io *io, bool *asi_present)
{
	uint32_t *csr = io->splicer->dev->csr;
	FD722_VideoInCS ics = FD722_VideoInCS_R(csr, FD722_INPUT_REG_INDEX(io));

	*asi_present = ics.cd && ics.modeLocked && (ics.mode == 3);
}

static void fd722_shift_io(const struct forward_splicer_io *io, s32 delta)
{
	FD722_VideoOutPhaseShift ph;

	//delta = -FD722_GENLOCK_SD_DELAY;
	delta = delta * 2 / 11;
	if (delta > 1080000)
		delta = delta % 1080000;
	if (delta < 0)
		delta = (delta % 1080000) + 1080000;

	ph.phase = delta;
	FD722_VideoOutPhaseShift_W(io->splicer->dev->csr, FD722_OUTPUT_REG_INDEX(io), ph);
}

static void fd722_toggle_streaming(const struct forward_splicer_io *io, bool enable)
{
	io->splicer->dev->cfg.toggle_streaming(io->splicer->dev, io->in_index, enable);
	io->splicer->dev->cfg.toggle_streaming(io->splicer->dev, io->out_index, enable);
}

static void fd722_toggle_bypass(const struct forward_splicer_io *io, bool bypass,
				unsigned int timeout_us)
{
	FD722_BypassTimeout bt;

	if (!FD722_IS_BYPASS(io->splicer->dev))
		return;

	if (bypass)
		bt.timeout = 0;
	else if (timeout_us > 60000000)
		bt.timeout = 0xFFFFFFFF;
	else
		bt.timeout = timeout_us / 2 * 125;

	FD722_BypassTimeout_W(io->splicer->dev->csr, FD722_INPUT_REG_INDEX(io), bt);
}

struct forward_splicer_dev_ops fd722_ops = {
	.irq_mask = fd722_irq_mask,
	.irq_info = fd722_irq_info,

	.buffer_map_address = fd722_buffer_map_address,

	.configure_io = fd722_configure_io,
	.io_state = fd722_io_state,
	.shift_io = fd722_shift_io,
	.toggle_streaming = fd722_toggle_streaming,
	.toggle_bypass = fd722_toggle_bypass,

	.num_io = 2,
	.num_in_buffers = FD722_NUMBER_IN_BUFFERS,
	.num_out_buffers = FD722_NUMBER_OUT_BUFFERS,
	.buffer_in_size = FD722_ASI_BUFFER_SIZE_PAGE,
	.buffer_out_size = FD722_ASI_BUFFER_SIZE_PAGE,
};

struct forward_splicer_dev_ops fd722m2_ops = {
	.irq_mask = fd722_irq_mask,
	.irq_info = fd722_irq_info,

	.buffer_map_address = fd722_buffer_map_address,

	.configure_io = fd722_configure_io,
	.io_state = fd722_io_state,
	.shift_io = fd722_shift_io,
	.toggle_streaming = fd722_toggle_streaming,
	.toggle_bypass = fd722_toggle_bypass,

	.num_io = 1,
	.num_in_buffers = FD722_NUMBER_IN_BUFFERS,
	.num_out_buffers = FD722_NUMBER_OUT_BUFFERS,
	.buffer_in_size = FD722_ASI_BUFFER_SIZE_PAGE,
	.buffer_out_size = FD722_ASI_BUFFER_SIZE_PAGE,
};
