/*
   forward-splicer-dev.c - DVB-ASI splicer driver for SoftLab-NSK Forward video 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-splicer-dev.h"
#include "sl_splicer_device_ioctl.h"

#include <linux/module.h>
#include <linux/vmalloc.h>

#define FORWARD_SPLICER_DEV_TYPE 1

extern struct class *module_class;

static int forward_splicer_dev_open(struct inode *inode, struct file *filp)
{
	struct forward_splicer_io *io;
	int result = 0;

	io = container_of(inode->i_cdev, struct forward_splicer_io, cdev);

	result = forward_ref_get(io->splicer->dev);
	if (result)
		return result;

	filp->private_data = io;

	return 0;
}

static int forward_splicer_dev_release(struct inode *inode, struct file *filp)
{
	struct forward_splicer_io *io = filp->private_data;
	if (io->splicer->ops->toggle_bypass)
		io->splicer->ops->toggle_bypass(io, true, 0);
	forward_ref_put(io->splicer->dev);
	return 0;
}

static long forward_splicer_dev_ioctl(struct file *filp, unsigned int ioctl_num,
				      unsigned long ioctl_param)
{
	struct forward_splicer_io *io = filp->private_data;
	unsigned int val;
	int offset;
	u8 packet_size;

	if (ioctl_num & IOC_IN) {
		if (copy_from_user(&val, (void *)ioctl_param, sizeof(unsigned int)))
			return -ENOMEM;
	}

	mutex_lock(&io->lock);
	packet_size = (io->fec ? 204 : 188);
	switch (ioctl_num) {
	case sl_splicer_get_fifo_size_id:
		val = ((io->stream_buffer_size - 1) / PAGE_SIZE) + 1;
		break;
	case sl_splicer_is_lock_id:
		val = io->locked;
		break;
	case sl_splicer_is_asi_detect_id:
		val = io->asi_detected;
		break;
	case sl_splicer_get_input_packet_size_id:
		val = io->fec;
		break;
	case sl_splicer_get_input_stuffing_id:
		// TODO:
		val = 1;
		break;
	case sl_splicer_set_output_packet_size_id:
		// TODO:
		break;
	case sl_splicer_get_output_packet_size_id:
		val = io->fec;
		break;
	case sl_splicer_set_output_stuffing_id:
		// TODO:
		break;
	case sl_splicer_get_output_stuffing_id:
		// TODO:
		val = 1;
		break;
	case sl_splicer_get_read_pos_id:
		val = (io->stream_rd_pos / packet_size) * packet_size;
		break;
	case sl_splicer_set_read_pos_id:
		offset = io->stream_wr_pos % packet_size;
		io->stream_rd_pos = (val / packet_size) * packet_size + offset;
		if (io->stream_rd_pos < 0)
			io->stream_rd_pos += io->stream_buffer_size;
		if (io->stream_rd_pos >= io->stream_buffer_size)
			io->stream_rd_pos = (io->stream_rd_pos % io->stream_buffer_size);
		break;
	case sl_splicer_get_write_pos_id:
		val = (io->stream_wr_pos / packet_size) * packet_size;
		break;
	case sl_splicer_get_interrupt_id:
		val = io->irq_counter;
		break;
	case sl_splicer_enable_output_id:
		io->out_en = val ? true : false;
		break;
	case sl_splicer_is_enable_output_id:
		val = io->out_en;
		break;
	case sl_splicer_enable_input_id:
		io->in_en = val ? true : false;
		break;
	case sl_splicer_is_enable_input_id:
		val = io->in_en;
		break;
	case sl_splicer_enable_bypass_id:
		io->bypass = val ? true : false;
		break;
	case sl_splicer_is_enable_bypass_id:
		val = io->bypass;
		break;
	case sl_splicer_get_firmware_svn_revision_id:
		val = io->splicer->dev->fw_version;
		break;
	case sl_splicer_get_firmware_rebuild_number_id:
		val = 0;
		break;
	case sl_splicer_keep_alive_bypass:
		if (io->splicer->ops->toggle_bypass)
			io->splicer->ops->toggle_bypass(io, io->bypass, val);
		break;
	default:
		mutex_unlock(&io->lock);
		return -EOPNOTSUPP;
	}
	mutex_unlock(&io->lock);

	if (ioctl_num & IOC_OUT) {
		if (copy_to_user((void *)ioctl_param, &val, sizeof(unsigned int)))
			return -ENOMEM;
	}

	return 0;
}

static int forward_splicer_dev_mmap(struct file *filp, struct vm_area_struct *vma)
{
	struct forward_splicer_io *io = filp->private_data;
	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
	size_t buf_size = PAGE_ALIGN(io->stream_buffer_size);
	size_t size = vma->vm_end - vma->vm_start;

	if (size > (buf_size - offset))
		return -EINVAL;

	remap_vmalloc_range(vma, io->stream_buffer, vma->vm_pgoff);

	return 0;
}

static const struct file_operations forward_splicer_dev_ops = {
	.owner = THIS_MODULE,
	.open = forward_splicer_dev_open,
	.release = forward_splicer_dev_release,
	.unlocked_ioctl = forward_splicer_dev_ioctl,
	.mmap = forward_splicer_dev_mmap,
};

void forward_splicer_dev_remove(struct forward_splicer_io *io)
{
	if (io->dev)
		device_destroy(module_class, io->dev_num);
	io->dev = NULL;

	if (io->cdev.owner == THIS_MODULE)
		cdev_del(&io->cdev);
	io->cdev.owner = NULL;
}

int forward_splicer_dev_init(struct forward_splicer_io *io)
{
	int result = 0;

	io->dev_num = forward_dev_number(io->splicer->dev, FORWARD_SPLICER_DEV_TYPE + io->in_index);

	cdev_init(&io->cdev, &forward_splicer_dev_ops);
	io->cdev.owner = THIS_MODULE;

	result = cdev_add(&io->cdev, io->dev_num, 1);
	if (result < 0) {
		forward_err(io->splicer->dev, "splicer: unable to add character device\n");
		io->cdev.owner = NULL;
		result = -ENODEV;
		goto fail;
	}

	io->dev = device_create(module_class, io->splicer->dev->dev, io->dev_num, io,
				"%s-splicer%d", io->splicer->dev->dev_name, io->in_index);

	if (IS_ERR(io->dev)) {
		forward_err(io->splicer->dev, "splicer: unable to create device\n");
		io->dev = NULL;
		result = -ENODEV;
		goto fail;
	}

	return 0;

fail:
	forward_splicer_dev_remove(io);
	return result;
}
