/*
   forward-fdio.c - driver for SoftLab-NSK FDIO video I/O core

   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-vdma.h"
#include "fdio_reg.h"
#include <linux/of.h>
#include <linux/stat.h>

struct forward_fdio_pin_config {
	u32 irq_number;
	u32 buffer_count;
};

struct forward_fdio_config {
	int num_inputs;
	int num_outputs;
	int num_ios;

	u32 dev_irq_mask;
	u32 dev_irq_data_mask;

	u32 io_region_size;
	u32 video_buffer_size;

	struct forward_fdio_pin_config *pins;
};

static int fdio_init(struct forward_dev *dev)
{
	struct device_node *ofn = dev->plat_dev->dev.of_node;
	s32 num_inputs = 0, num_outputs = 0, num_ios = 0;
	s32 total_io = 0;
	u32 dma_size = 0;
	struct forward_fdio_config *priv;
	int i;
	int cell_count;

	if (!ofn)
		return -EINVAL;

	of_property_read_u32(ofn, "num-inputs", &num_inputs);
	of_property_read_u32(ofn, "num-outputs", &num_outputs);
	of_property_read_u32(ofn, "num-ios", &num_ios);
	of_property_read_u32(ofn, "dma-size", &dma_size);

	dev_info(
		dev->parent_dev,
		"found FDIO core with %d inputs, %d outputs, %d bidirectional and %u MiB DMA region",
		num_inputs, num_outputs, num_ios, dma_size / 1024 / 1024);
	total_io = num_inputs + num_outputs + num_ios;

	priv = devm_kzalloc(dev->parent_dev,
			    sizeof(struct forward_fdio_config) +
				    total_io * sizeof(struct forward_fdio_pin_config),
			    GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->pins = ((void *)priv) + sizeof(struct forward_fdio_config);
	priv->num_inputs = num_inputs;
	priv->num_outputs = num_outputs;
	priv->num_ios = num_ios;

	dev->cfg.vdma_size_mb = dma_size / 1024 / 1024;
	dev->cfg.io_count = num_inputs + num_outputs + num_ios;
	dev->cfg.private = priv;

	of_property_read_u32(ofn, "hw-size", &priv->io_region_size);
	of_property_read_u32(ofn, "hw-video-buffer-size", &priv->video_buffer_size);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 15, 0)
	cell_count = of_property_count_elems_of_size(ofn, "hw-interrupts", 4);
#else
	cell_count = of_find_property(ofn, "hw-interrupts", NULL)->length / 4;
#endif
	for (i = 0; i < cell_count; i++) {
		u32 val = 0;
		if (!of_property_read_u32_index(ofn, "hw-interrupts", i, &val))
			priv->dev_irq_mask |= (1 << val);
	}

	for (i = 0; i < total_io; i++) {
		u32 val = 0;

		if (!of_property_read_u32_index(ofn, "hw-interrupts", i, &val))
			priv->pins[i].irq_number = val;

		if (!of_property_read_u32_index(ofn, "hw-buffers", i, &val))
			priv->pins[i].buffer_count = val;

		if (priv->pins[i].buffer_count > 0)
			priv->dev_irq_data_mask |= ((priv->pins[i].buffer_count - 1)
						    << (priv->pins[i].irq_number + 1));
	}

	return 0;
}

static void fdio_fini(struct forward_dev *dev)
{
	if (dev->cfg.private)
		devm_kfree(dev->parent_dev, dev->cfg.private);
	dev->cfg.private = NULL;
}

static void fdio_read_id(struct forward_dev *dev)
{
	(void)dev;
}

static void fdio_enable_interrupts(struct forward_dev *dev, const forward_irq_t *interrupts)
{
	struct forward_fdio_config *c = dev->cfg.private;
	u32 irq = ioread32(&dev->csr[FDIO_IRQEnable_A]);
	irq |= interrupts->irq[0] & c->dev_irq_mask;
	iowrite32(irq, &dev->csr[FDIO_IRQEnable_A]);
}

static void fdio_disable_interrupts(struct forward_dev *dev, const forward_irq_t *interrupts)
{
	struct forward_fdio_config *c = dev->cfg.private;
	u32 irq = ioread32(&dev->csr[FDIO_IRQEnable_A]);
	irq &= ~(interrupts->irq[0] & c->dev_irq_mask);
	iowrite32(irq, &dev->csr[FDIO_IRQEnable_A]);
	iowrite32(interrupts->irq[0] & c->dev_irq_mask, &dev->csr[FDIO_IRQFlags_A]);
}

static bool fdio_read_interrupts(struct forward_dev *dev, forward_irq_t *interrupts,
				 forward_irq_t *data, forward_irq_t *data_mask)
{
	struct forward_fdio_config *c = dev->cfg.private;

	u32 irq = ioread32(&dev->csr[FDIO_IRQFlags_A]);
	iowrite32(irq, &dev->csr[FDIO_IRQFlags_A]);

	interrupts->irq[0] = irq & c->dev_irq_mask;
	data->irq[0] = irq & c->dev_irq_data_mask;
	data_mask->irq[0] = c->dev_irq_data_mask;

	return (irq & c->dev_irq_mask) ? true : false;
}

static void fdio_enable_vdma(struct forward_dev *dev, u32 map_addr)
{
	FDIO_VDMADescriptor desc = { .address = map_addr };
	FDIO_VDMA vdma = { .enable = 1 };
	FDIO_VDMADescriptor_W(dev->csr, desc);
	FDIO_VDMA_W(dev->csr, vdma);
}

static void fdio_disable_vdma(struct forward_dev *dev)
{
	FDIO_VDMA vdma = { .enable = 0 };
	FDIO_VDMA_W(dev->csr, vdma);
}

static enum forward_io_type fdio_io_type(struct forward_dev *dev, int io)
{
	struct forward_fdio_config *c = dev->cfg.private;

	if (io < c->num_inputs)
		return FORWARD_IO_INPUT;
	else if (io < c->num_inputs + c->num_outputs)
		return FORWARD_IO_OUTPUT;
	else
		return FORWARD_IO_BIDIR;
}

static int fdio_io_number(struct forward_dev *dev, int io)
{
	struct forward_fdio_config *c = dev->cfg.private;

	if (io < c->num_inputs)
		return io + 1;
	else if (io < c->num_inputs + c->num_outputs)
		return io - c->num_inputs + 1;
	else
		return io - max(c->num_inputs, c->num_outputs) + 1;
}

static enum forward_io_state fdio_io_state(struct forward_dev *dev, int io)
{
	struct forward_fdio_config *c = dev->cfg.private;

	if (io < c->num_inputs)
		return FORWARD_IO_RX;
	else if (io < c->num_inputs + c->num_outputs)
		return FORWARD_IO_TX;
	else
		return FORWARD_IO_TXRX;
}

static int fdio_set_io_state(struct forward_dev *dev, int io, enum forward_io_state state)
{
	struct forward_fdio_config *c = dev->cfg.private;

	forward_vdma_init_region(dev->vdma, (unsigned long)io * c->io_region_size,
				 c->io_region_size, (state == FORWARD_IO_RX) ? true : false);

	return 0;
}

static void fdio_toggle_streaming(struct forward_dev *dev, int io, bool enabled)
{
}

static ssize_t fdio_attr_show_version(struct device *dev, struct device_attribute *attr, char *buf);

static DEVICE_ATTR(version, S_IRUGO, fdio_attr_show_version, NULL);

static ssize_t fdio_attr_show_version(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct forward_dev *fdev = dev_get_drvdata(dev);
	FDIO_StaticVersion version = FDIO_StaticVersion_R(fdev->csr);

	return scnprintf(buf, PAGE_SIZE, "%dr%d\n", version.major, version.revision);
	return 0;
}

static struct attribute *fdio_dev_attrs[] = {
	&dev_attr_version.attr,
	NULL,
};

static struct attribute_group fdio_attr_group = {
	.attrs = fdio_dev_attrs,
};

struct forward_dev_config fdio_cfg = {
	.vdma_size_mb = 256,
	.io_count = 0,

	.attributes = &fdio_attr_group,

	.private = NULL,

	.init = fdio_init,
	.fini = fdio_fini,

	.read_id = fdio_read_id,

	.enable_interrupts = fdio_enable_interrupts,
	.disable_interrupts = fdio_disable_interrupts,
	.read_interrupts = fdio_read_interrupts,

	.enable_vdma = fdio_enable_vdma,
	.disable_vdma = fdio_disable_vdma,

	.io_type = fdio_io_type,
	.io_number = fdio_io_number,
	.io_state = fdio_io_state,
	.set_io_state = fdio_set_io_state,

	.toggle_streaming = fdio_toggle_streaming,
};
