/*
   forward-fd720.c - driver for SoftLab-NSK FD720 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.h"
#include "forward-vdma.h"
#include "forward-irq.h"

#include "fd720_reg.h"

#include <linux/stat.h>

#define FD720_ALL_INTERRUPTS ((1 << 0) | (1 << 3) | (1 << 6) | (1 << 8))

#define FD720_ALL_INTERRUPTS_DATA_MASK ((3 << 1) | (3 << 4) | (1 << 7) | (1 << 9))

static int fd720_init(struct forward_dev *dev)
{
	return 0;
}
static void fd720_fini(struct forward_dev *dev)
{
}

static void fd720_reboot(struct forward_dev *dev)
{
	FD720_GlobalCSR g = FD720_GlobalCSR_R(dev->csr);
	g.reboot = 1;
	FD720_GlobalCSR_W(dev->csr, g);
	// Board dies in ~2-5s
}

static void fd720_read_id(struct forward_dev *dev)
{
	int cap_addr = pci_find_ext_capability(dev->pci_dev, PCI_EXT_CAP_ID_DSN);

	FD720_SoftID id = FD720_SoftID_R(dev->csr);
	FD720_StaticVersion v = FD720_StaticVersion_R(dev->csr);

	dev->soft_id = (s32)id.id;

	if (dev->soft_id < 0)
		dev_warn(dev->parent_dev, "Invalid device serial: %lld\n", dev->soft_id);

	if (cap_addr) {
		u32 hi, lo;
		pci_read_config_dword(dev->pci_dev, cap_addr + 4, &lo);
		pci_read_config_dword(dev->pci_dev, cap_addr + 8, &hi);
		dev->hard_id = ((u64)hi << 32) | (u64)lo;
	} else {
		dev_warn(dev->parent_dev, "Cannot get device hardware serial\n");
		dev->hard_id = (u64)dev->soft_id;
	}

	dev->fw_version = (v.hardware << 24) | (v.major << 16) | v.revision;
}

static void fd720_enable_interrupts(struct forward_dev *dev, const forward_irq_t *interrupts)
{
	u32 irq = ioread32(&dev->csr[FD720_IRQEnable_A]);
	irq |= interrupts->irq[0] & FD720_ALL_INTERRUPTS;
	iowrite32(irq, &dev->csr[FD720_IRQEnable_A]);
}

static void fd720_disable_interrupts(struct forward_dev *dev, const forward_irq_t *interrupts)
{
	u32 irq = ioread32(&dev->csr[FD720_IRQEnable_A]);
	irq &= ~(interrupts->irq[0] & FD720_ALL_INTERRUPTS);
	iowrite32(irq, &dev->csr[FD720_IRQEnable_A]);
	iowrite32(interrupts->irq[0] & FD720_ALL_INTERRUPTS, &dev->csr[FD720_IRQFlags_A]);
}

static bool fd720_read_interrupts(struct forward_dev *dev, forward_irq_t *interrupts,
				  forward_irq_t *data, forward_irq_t *data_mask)
{
	u32 irq = ioread32(&dev->csr[FD720_IRQFlags_A]);
	iowrite32((irq & FD720_ALL_INTERRUPTS), &dev->csr[FD720_IRQFlags_A]);
	interrupts->irq[0] = irq & FD720_ALL_INTERRUPTS;
	data->irq[0] = irq & FD720_ALL_INTERRUPTS_DATA_MASK;
	data_mask->irq[0] = FD720_ALL_INTERRUPTS_DATA_MASK;

	return (irq & FD720_ALL_INTERRUPTS) ? true : false;
}

static void fd720_enable_vdma(struct forward_dev *dev, u32 map_addr)
{
	FD720_VDMADescriptor desc = { .address = map_addr };
	FD720_VDMA vdma = { .enable = 1 };
	FD720_VDMADescriptor_W(dev->csr, desc);
	FD720_VDMA_W(dev->csr, vdma);
}

static void fd720_disable_vdma(struct forward_dev *dev)
{
	FD720_VDMA vdma = { .enable = 0 };
	FD720_VDMA_W(dev->csr, vdma);
}

static enum forward_io_type fd720_io_type(struct forward_dev *dev, int io)
{
	return FORWARD_IO_INPUT;
}

static int fd720_io_number(struct forward_dev *dev, int io)
{
	return io + 1;
}

static enum forward_io_state fd720_io_state(struct forward_dev *dev, int io)
{
	FD720_VideoInCS cs = FD720_VideoInCS_R(dev->csr, io);
	return cs.enable ? FORWARD_IO_RX : FORWARD_IO_DISABLED;
}

static int fd720_set_io_state(struct forward_dev *dev, int io, enum forward_io_state state)
{
	FD720_VideoInCS cs;

	if ((state != FORWARD_IO_DISABLED) && (state != FORWARD_IO_RX))
		return -EOPNOTSUPP;

	cs = FD720_VideoInCS_R(dev->csr, io);
	cs.enable = (state == FORWARD_IO_DISABLED) ? 0 : 1;
	FD720_VideoInCS_W(dev->csr, io, cs);

	forward_vdma_init_region(dev->vdma, (io * 0x20000000UL), 0x20000000UL, true);

	return 0;
}

static void fd720_toggle_streaming(struct forward_dev *dev, int number, bool enabled)
{
	FD720_VideoInCS cs = FD720_VideoInCS_R(dev->csr, number);
	cs.capture = enabled ? 1 : 0;
	FD720_VideoInCS_W(dev->csr, number, cs);
}

static ssize_t fd720_attr_show_adc(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t fd720_attr_show_version(struct device *dev, struct device_attribute *attr,
				       char *buf);
static ssize_t fd720_attr_show_dma(struct device *dev, struct device_attribute *attr, char *buf);

static DEVICE_ATTR(temperature, S_IRUGO, fd720_attr_show_adc, NULL);
static DEVICE_ATTR(vcore, S_IRUGO, fd720_attr_show_adc, NULL);
static DEVICE_ATTR(vaux, S_IRUGO, fd720_attr_show_adc, NULL);
static DEVICE_ATTR(version, S_IRUGO, fd720_attr_show_version, NULL);
static DEVICE_ATTR(dma_lat_max, S_IRUGO, fd720_attr_show_dma, NULL);
static DEVICE_ATTR(dma_lat_avg, S_IRUGO, fd720_attr_show_dma, NULL);
static DEVICE_ATTR(dma_perf_read, S_IRUGO, fd720_attr_show_dma, NULL);
static DEVICE_ATTR(dma_perf_write, S_IRUGO, fd720_attr_show_dma, NULL);

static ssize_t fd720_attr_show_adc(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct forward_dev *fdev = dev_get_drvdata(dev);
	int dec = 0, frac = 0;

	if (attr == &dev_attr_temperature) {
		FD720_TemperatureMonitor temp = FD720_TemperatureMonitor_R(fdev->csr);
		int value = temp.temperature * 503975 / 1024 - 273150;
		dec = value / 1000;
		frac = abs(value % 1000);
	} else if (attr == &dev_attr_vcore) {
		FD720_VCoreMonitor v = FD720_VCoreMonitor_R(fdev->csr);
		int value = v.voltage * 3 * 1000 / 1024;
		dec = value / 1000;
		frac = value % 1000;
	} else if (attr == &dev_attr_vaux) {
		FD720_VAuxMonitor v = FD720_VAuxMonitor_R(fdev->csr);
		int value = v.voltage * 3 * 1000 / 1024;
		dec = value / 1000;
		frac = value % 1000;
	}
	return scnprintf(buf, PAGE_SIZE, "%d.%03d\n", dec, frac);
}

static ssize_t fd720_attr_show_dma(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct forward_dev *fdev = dev_get_drvdata(dev);
	long value = 0;

	if (attr == &dev_attr_dma_perf_read) {
		FD720_PCIEPerfomance perf = FD720_PCIEPerfomance_R(fdev->csr);
		value = (long)perf.rxCounter * 16 * 125000000 / 65535;
	} else if (attr == &dev_attr_dma_perf_write) {
		FD720_PCIEPerfomance perf = FD720_PCIEPerfomance_R(fdev->csr);
		value = (long)perf.txCounter * 16 * 125000000 / 65535;
	} else if (attr == &dev_attr_dma_lat_max) {
		FD720_PCIERxMaxLatency lat = FD720_PCIERxMaxLatency_R(fdev->csr);
		value = (long)lat.maxLatency * 16;
	} else if (attr == &dev_attr_dma_lat_avg) {
		FD720_PCIERxMaxLatency lat = FD720_PCIERxMaxLatency_R(fdev->csr);
		FD720_PCIERxTotalLatency tlat = FD720_PCIERxTotalLatency_R(fdev->csr);
		if (lat.numRequests)
			value = (long)tlat.latency * 16 / ((long)lat.numRequests * 4);
	}
	return scnprintf(buf, PAGE_SIZE, "%ld\n", value);
}

static ssize_t fd720_attr_show_version(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct forward_dev *fdev = dev_get_drvdata(dev);
	FD720_StaticVersion version = FD720_StaticVersion_R(fdev->csr);
	int hw_major = 0, hw_minor = 0;

	if (version.hardware == 0) {
		hw_major = 1;
		hw_minor = 0;
	} else if (version.hardware == 1) {
		hw_major = 1;
		hw_minor = 1;
	}

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

static struct attribute *fd720_dev_attrs[] = {
	&dev_attr_temperature.attr, &dev_attr_vcore.attr,	  &dev_attr_vaux.attr,
	&dev_attr_version.attr,	    &dev_attr_dma_perf_read.attr, &dev_attr_dma_perf_write.attr,
	&dev_attr_dma_lat_max.attr, &dev_attr_dma_lat_avg.attr,	  NULL,
};

static struct attribute_group fd720_attr_group = {
	.attrs = fd720_dev_attrs,
};

struct forward_dev_config fd720_cfg = {
	.vdma_size_mb = 1024,
	.sdma_size = 0,
	.sdma_rings = 0,
	.io_count = 2,

	.attributes = &fd720_attr_group,

	.private = NULL,

	.init = fd720_init,
	.fini = fd720_fini,
	.reboot = fd720_reboot,

	.read_id = fd720_read_id,

	.enable_interrupts = fd720_enable_interrupts,
	.disable_interrupts = fd720_disable_interrupts,
	.read_interrupts = fd720_read_interrupts,

	.enable_vdma = fd720_enable_vdma,
	.disable_vdma = fd720_disable_vdma,

	.io_type = fd720_io_type,
	.io_number = fd720_io_number,
	.io_state = fd720_io_state,
	.set_io_state = fd720_set_io_state,

	.toggle_streaming = fd720_toggle_streaming,
};
