/*
   forward-splicer.c - splicer driver for SoftLab-NSK Forward boards

   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.h"
#include "forward-splicer.h"
#include "forward-splicer-asi.h"
#include "forward-splicer-dev.h"
#include "forward-vdma.h"

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/vmalloc.h>

#define FORWARD_SPLICER_MODE_NAME "splicer"
#define FORWARD_SPLICER_CLASS_NAME "splicer"
#define FORWARD_SPLICER_FIFO_SIZE 20000
#define FORWARD_SPLICER_LOCK_THRESHOLD 100
#define FORWARD_SPLICER_UNLOCK_THRESHOLD 500
#define FORWARD_SPLICER_FREQ_THRESHOLD 12

extern struct forward_splicer_dev_ops fd722_ops;
extern struct forward_splicer_dev_ops fd722m2_ops;
extern struct forward_splicer_dev_ops fd922_ops;
extern struct forward_splicer_dev_ops fd788_ops;
struct class *module_class = 0;

static int forward_splicer_thread(void *p)
{
	struct forward_splicer_io *io = p;
	struct forward_splicer *splicer = io->splicer;
	struct forward_irq_listener listener;
	bool shifting = false;
	s32 timedelta;
	u8 *hw_in_buffer, *hw_out_buffer;
	u32 in_time = 0, out_time = 0;

	io->asi_detected = false;
	io->locked = false;

	splicer->ops->configure_io(io);

	forward_irq_listener_init(&listener);
	listener.type = FORWARD_IRQ_LISTENER_WAIT;
	listener.mask = splicer->ops->irq_mask(io);
	forward_irq_listener_add(splicer->dev, &listener);

	while (!kthread_should_stop()) {
		forward_irq_t irq = forward_irq_listener_wait_kthread(&listener, true, 0);
		bool in_irq, out_irq;
		int in_buffer, out_buffer;
		size_t in_buffer_size, out_buffer_size;
		bool n_asi;
		s32 n_timedelta;
		s32 n_freqdelta;
		bool has_irq;

		if (kthread_should_stop())
			break;

		has_irq = splicer->ops->irq_info(io, &irq, &in_irq, &in_buffer, &in_buffer_size,
						 &in_time, &out_irq, &out_buffer, &out_buffer_size,
						 &out_time);

		if (!has_irq)
			continue;

		if (out_irq) {
			splicer->ops->io_state(io, &n_asi);

			if (io->asi_detected && !n_asi)
				forward_warn(splicer->dev, "splicer%d: input lost\n", io->in_index);
			else if (!io->asi_detected && n_asi)
				forward_info(splicer->dev, "splicer%d: input ok\n", io->in_index);
			io->asi_detected = n_asi;

			n_timedelta = (s32)(in_time - out_time);
			n_freqdelta = n_timedelta - timedelta;

			if (io->asi_detected && io->locked &&
			    (abs(n_timedelta) > FORWARD_SPLICER_UNLOCK_THRESHOLD)) {
				forward_warn(splicer->dev, "splicer%d: desynced\n", io->in_index);
				io->locked = false;
			} else if (io->asi_detected && !io->locked &&
				   (abs(n_timedelta) < FORWARD_SPLICER_LOCK_THRESHOLD) &&
				   (abs(n_timedelta - timedelta) <
				    FORWARD_SPLICER_FREQ_THRESHOLD)) {
				forward_warn(splicer->dev, "splicer%d: synced\n", io->in_index);
				io->locked = true;
			}

			if (io->asi_detected && !io->locked && !shifting &&
			    (abs(n_freqdelta) < FORWARD_SPLICER_FREQ_THRESHOLD) &&
			    (abs(n_timedelta) > FORWARD_SPLICER_UNLOCK_THRESHOLD)) {
				forward_info(splicer->dev,
					     "splicer%d: syncing output to input (d = %lldns)\n",
					     io->in_index, (s64)n_timedelta * 2000 / 297);
				splicer->ops->shift_io(io, n_timedelta);
				shifting = true;
			} else if (abs(n_timedelta - timedelta) > 100) {
				shifting = false;
			}

			io->irq_counter++;
			timedelta = n_timedelta;
		}

		if (!io->streaming && io->asi_detected) {
			forward_info(splicer->dev, "splicer%d: starting...\n", io->in_index);
			splicer->ops->toggle_streaming(io, true);
			io->streaming = true;
		}

		if (!io->streaming)
			continue;

		hw_in_buffer = &io->in_buffer[in_buffer * splicer->ops->buffer_in_size];
		hw_out_buffer = &io->out_buffer[out_buffer * splicer->ops->buffer_out_size];

		if (out_irq && (!in_irq || !io->locked))
			memset(hw_out_buffer, 0, out_buffer_size);
		else if (out_irq)
			forward_splicer_process_asi(io, hw_in_buffer, hw_out_buffer,
						    in_buffer_size);
	}

	forward_info(splicer->dev, "splicer%d: stopping...\n", io->in_index);
	splicer->ops->toggle_streaming(io, false);
	io->streaming = false;

	forward_irq_listener_remove(splicer->dev, &listener);

	return 0;
}

static void forward_splicer_remove(struct forward_dev *dev, void **private)
{
	struct forward_splicer *splicer = *private;
	const struct forward_splicer_dev_ops *ops = splicer->ops;
	int i, j;

	if (splicer->io) {
		for (i = 0; i < ops->num_io; i++) {
			struct forward_splicer_io *io = &splicer->io[i];

			if (io->thread) {
				kthread_stop(io->thread);
				io->thread = NULL;
			}

			forward_splicer_dev_remove(io);

			if (io->stream_buffer)
				vfree(io->stream_buffer);
			io->stream_buffer = NULL;

			if (io->tmp_buffer)
				vfree(io->tmp_buffer);
			io->tmp_buffer = NULL;

			if (io->in_buffer) {
				for (j = 0; j < ops->num_in_buffers; j++)
					forward_vdma_unmap_buf(dev->vdma, io,
							       ops->buffer_map_address(io, j, true),
							       ops->buffer_in_size);
				vfree(io->in_buffer);
			}
			io->in_buffer = NULL;

			if (io->out_buffer) {
				for (j = 0; j < ops->num_out_buffers; j++)
					forward_vdma_unmap_buf(dev->vdma, io,
							       ops->buffer_map_address(io, j,
										       false),
							       ops->buffer_out_size);
				vfree(io->out_buffer);
			}
			io->out_buffer = NULL;
			forward_vdma_put_all_regions(dev->vdma, io);
		}
		devm_kfree(dev->dev, splicer->io);
	}
	splicer->io = NULL;

	*private = NULL;
	devm_kfree(dev->dev, splicer);
}

static int forward_splicer_probe(struct forward_dev *dev, void **private)
{
	int result = 0;
	struct forward_splicer *splicer;
	const struct forward_splicer_dev_ops *ops;
	int i, j;

	if (!forward_has_mode(dev, FORWARD_SPLICER_MODE_NAME))
		return -EOPNOTSUPP;

	switch (dev->type) {
	case FORWARD_FD722:
		ops = &fd722_ops;
		break;
	case FORWARD_FD722M2:
		ops = &fd722m2_ops;
		break;
	case FORWARD_FD922:
		ops = &fd922_ops;
		break;
	case FORWARD_FD788:
		ops = &fd788_ops;
		break;
	default:
		return -EOPNOTSUPP;
	}

	splicer = devm_kzalloc(dev->dev, sizeof(struct forward_splicer), GFP_KERNEL);
	if (!splicer)
		return -ENOMEM;

	splicer->dev = dev;
	*private = splicer;
	splicer->ops = ops;

	splicer->io =
		devm_kzalloc(dev->dev, ops->num_io * sizeof(struct forward_splicer_io), GFP_KERNEL);
	if (!splicer->io) {
		result = -ENOMEM;
		goto fail;
	}

	for (i = 0; i < ops->num_io; i++) {
		struct forward_splicer_io *io = &splicer->io[i];
		io->splicer = splicer;
		mutex_init(&io->lock);

		io->bypass = 1;
		io->in_en = 0;
		io->out_en = 0;
		io->irq_counter = 0;

		switch (dev->type) {
		case FORWARD_FD722:
			io->in_index = i;
			io->out_index = i + 2;
			break;
		case FORWARD_FD722M2:
			io->in_index = 0;
			io->out_index = 3;
			break;
		case FORWARD_FD922:
			io->in_index = i;
			io->out_index = i + 2;
			break;
		case FORWARD_FD788:
			io->in_index = i;
			io->out_index = i + 4;
			break;
		default:
			return -EOPNOTSUPP;
		}

		io->fec = false;

		io->in_buffer = vzalloc(ops->buffer_in_size * ops->num_in_buffers);
		if (!io->in_buffer) {
			result = -ENOMEM;
			goto fail;
		}

		for (j = 0; j < ops->num_in_buffers; j++) {
			u32 address = ops->buffer_map_address(io, j, true);
			size_t size = ops->buffer_in_size;

			result = forward_vdma_get_region(dev->vdma, io, address, size);
			if (result) {
				forward_err(dev, "Cannot request input VDMA region!");
				goto fail;
			}
			result = forward_vdma_map_kernel_buf(
				dev->vdma, io, &io->in_buffer[j * size], address, size, true);
			if (result) {
				forward_err(dev, "Cannot map input buffer!");
				goto fail;
			}
		}

		io->out_buffer = vzalloc(ops->buffer_out_size * ops->num_out_buffers);
		if (!io->out_buffer) {
			result = -ENOMEM;
			goto fail;
		}

		for (j = 0; j < ops->num_out_buffers; j++) {
			u32 address = ops->buffer_map_address(io, j, false);
			size_t size = ops->buffer_out_size;

			result = forward_vdma_get_region(dev->vdma, io, address, size);
			if (result) {
				forward_err(dev, "Cannot request output VDMA region!");
				goto fail;
			}
			result = forward_vdma_map_kernel_buf(dev->vdma, io,
							     &io->out_buffer[j * size], address,
							     ops->buffer_out_size, false);
			if (result) {
				forward_err(dev, "Cannot map output buffer!");
				goto fail;
			}
		}

		io->tmp_buffer = vzalloc(ops->buffer_in_size);
		if (!io->tmp_buffer) {
			result = -ENOMEM;
			goto fail;
		}

		io->stream_buffer_size = (FORWARD_SPLICER_FIFO_SIZE * PAGE_SIZE / 188) * 188;
		io->stream_buffer = vmalloc_user(FORWARD_SPLICER_FIFO_SIZE * PAGE_SIZE);
		io->stream_wr_pos = 0;
		io->stream_rd_pos = 0;
		if (!io->stream_buffer) {
			result = -ENOMEM;
			goto fail;
		}

		result = forward_splicer_dev_init(io);
		if (result) {
			forward_err(dev, "Cannot create splicer dev!");
			goto fail;
		}

		io->thread = kthread_run(forward_splicer_thread, io, "fd-%llu-spl%d", dev->soft_id,
					 io->in_index);
	}

	forward_info(dev, "Created %d splicer devices: \n", ops->num_io);
	for (i = 0; i < ops->num_io; i++)
		forward_info(dev, "  IN%d -> OUT%d\n", dev->io[splicer->io[i].in_index].number,
			     dev->io[splicer->io[i].out_index].number);

	return 0;

fail:
	forward_splicer_remove(dev, private);

	return result;
}

static int forward_splicer_reprobe(struct forward_dev *dev, void **private)
{
	if (!forward_has_mode(dev, FORWARD_SPLICER_MODE_NAME))
		return -EOPNOTSUPP;

	return 0;
}

static void forward_splicer_io_changed(struct forward_dev *dev, struct forward_io *io,
				       enum forward_io_state state, void **private)
{
}

static bool forward_splicer_available(struct forward_dev *dev)
{
	switch (dev->type) {
	case FORWARD_FD722:
	case FORWARD_FD722M2:
	case FORWARD_FD788:
	case FORWARD_FD922:
		return true;
	default:
		return false;
	}
}

static struct forward_extension forward_splicer_ext = {
	.name = FORWARD_SPLICER_MODE_NAME,
	.version = FORWARD_VERSION_CODE,
	.available = forward_splicer_available,
	.probe = forward_splicer_probe,
	.reprobe = forward_splicer_reprobe,
	.remove = forward_splicer_remove,
	.io_changed = forward_splicer_io_changed,
};

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 2, 0)) || \
	(RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(9, 3))
static char *forward_splicer_devnode(const struct device *dev, umode_t *mode)
#else
static char *forward_splicer_devnode(struct device *dev, umode_t *mode)
#endif
{
	size_t size = strlen(dev_name(dev)) + sizeof(FORWARD_CLASS_NAME) + 2;
	char *result = kmalloc(size, GFP_KERNEL);

	snprintf(result, size, "%s/%s", FORWARD_CLASS_NAME, dev_name(dev));

	return result;
}

static int forward_splicer_init(void)
{
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)) || \
	(RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(9, 4))
	module_class = class_create(FORWARD_SPLICER_CLASS_NAME);
#else
	module_class = class_create(THIS_MODULE, FORWARD_SPLICER_CLASS_NAME);
#endif

	if (IS_ERR(module_class)) {
		printk(KERN_ERR "forward-splicer: Cannot create device class!\n");
		return -ENOMEM;
	}

	module_class->devnode = &forward_splicer_devnode;
	return forward_register_extension(&forward_splicer_ext);
}

static void forward_splicer_exit(void)
{
	forward_unregister_extension(&forward_splicer_ext);
	if (module_class)
		class_destroy(module_class);
}

module_init(forward_splicer_init);
module_exit(forward_splicer_exit);

MODULE_LICENSE("GPL");
