/*
   forward-splicer-fd922.h - splicer driver for SoftLab-NSK FD922 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 "fd922_reg.h"
#include "forward-pll.h"

#include <linux/delay.h>

#define FD922_INPUT_REG_INDEX(io) (io->in_index % 2)
#define FD922_OUTPUT_REG_INDEX(io) ((io->out_index - 2) % 2)

#define FD922_INPUT_IRQ_NUM(io) ((io)->in_index * 3)
#define FD922_OUTPUT_IRQ_NUM(io) ((io)->out_index * 3)

#define FD922_BUFFER_NEXT(buf, offset, count) ((((unsigned int)(buf)) + (offset)) % (count))
#define FD922_NUMBER_IN_BUFFERS (4)
#define FD922_NUMBER_OUT_BUFFERS (2)

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

static forward_irq_t fd922_irq_mask(const struct forward_splicer_io *io)
{
	forward_irq_t result = { 0 };
	forward_irq_flags_set_one(result, FD922_INPUT_IRQ_NUM(io));
	forward_irq_flags_set_one(result, FD922_OUTPUT_IRQ_NUM(io));
	return result;
}

inline bool fd922_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, FD922_INPUT_IRQ_NUM(io)))
		return false;

	buf = forward_irq_extract_data(*irq, FD922_INPUT_IRQ_NUM(io) + 1, 2);
	buf = FD922_BUFFER_NEXT(buf, -1, FD922_NUMBER_IN_BUFFERS);

	*size = FD922_ASI_BUFFER_SIZE;
	*in_buffer = buf;
	*timestamp =
		FD922_VideoInIRQRefTime_R(io->splicer->dev->csr, FD922_INPUT_REG_INDEX(io)).time;

	return true;
}

inline bool fd922_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, FD922_OUTPUT_IRQ_NUM(io)))
		return false;

	buf = forward_irq_extract_data(*irq, FD922_OUTPUT_IRQ_NUM(io) + 1, 1);
	buf = FD922_BUFFER_NEXT(buf, +1, FD922_NUMBER_OUT_BUFFERS);

	*size = FD922_ASI_BUFFER_SIZE;
	*out_buffer = buf;
	*timestamp =
		FD922_VideoOutIRQRefTime_R(io->splicer->dev->csr, FD922_OUTPUT_REG_INDEX(io)).time;

	return true;
}

static bool fd922_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 = fd922_irq_info_in(io, irq, in_buffer, in_size, in_timestamp);
	*out = fd922_irq_info_out(io, irq, out_buffer, out_size, out_timestamp);
	return *in || *out;
}

static u32 fd922_buffer_map_address(const struct forward_splicer_io *io, int buffer, bool in)
{
	if (in)
		return io->in_index * 0x20000000UL + buffer * 0x04000000UL;
	else
		return io->out_index * 0x20000000UL + buffer * 0x04000000UL;
}

static void fd922_configure_io(const struct forward_splicer_io *io)
{
	uint32_t *csr = io->splicer->dev->csr;
	int idx = FD922_INPUT_REG_INDEX(io), odx = FD922_OUTPUT_REG_INDEX(io);
	FD922_VideoOutCS ocs;
	FD922_VideoOutLine line;
	FD922_VideoOutPixel pixel;
	FD922_VideoOutStart start;
	FD922_VideoOutStop stop;
	FD922_VideoOutField field;
	FD922_VideoOutOddDataCount oddC;
	FD922_VideoOutEvenDataCount evenC;
	FD922_VideoInCS ics;

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

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

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

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

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

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

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

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

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

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

	ics = FD922_VideoInCS_R(csr, idx);
	ics.captureRaw = 1;
	FD922_VideoInCS_W(csr, idx, ics);
}

static void fd922_io_state(const struct forward_splicer_io *io, bool *asi_present)
{
	uint32_t *csr = io->splicer->dev->csr;
	FD922_VideoInCS ics = FD922_VideoInCS_R(csr, FD922_INPUT_REG_INDEX(io));

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

static void fd922_shift_io(const struct forward_splicer_io *io, s32 delta)
{
	FD922_VideoOutPhaseShift ph;

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

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

static void fd922_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);
}

struct forward_splicer_dev_ops fd922_ops = {
	.irq_mask = fd922_irq_mask,
	.irq_info = fd922_irq_info,

	.buffer_map_address = fd922_buffer_map_address,

	.configure_io = fd922_configure_io,
	.io_state = fd922_io_state,
	.shift_io = fd922_shift_io,
	.toggle_streaming = fd922_toggle_streaming,

	.num_io = 2,
	.num_in_buffers = FD922_NUMBER_IN_BUFFERS,
	.num_out_buffers = FD922_NUMBER_OUT_BUFFERS,
	.buffer_in_size = FD922_ASI_BUFFER_SIZE_PAGE,
	.buffer_out_size = FD922_ASI_BUFFER_SIZE_PAGE,
};
