/*
   forward-fd2110.c - driver for SoftLab-NSK FD2110 video board

   Copyright (C) 2017 - 2024 Konstantin Oblaukhov <oblaukhov@sl.iae.nsk.su>

   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 "forward-mcap.h"
#include "forward-fd2110.h"

#include "fd2110_reg.h"
#include "forward-lmk05028.h"

#include <linux/stat.h>
#include <linux/firmware.h>
#include <linux/delay.h>

#define FD2110_ALL_INTERRUPTS                                                              \
	((1 << 0) | (1 << 3) | (1 << 12) | (1 << 14) | (1 << 16) | (1 << 18) | (1 << 20) | \
	 (1 << 21) | (1 << 22) | (1 << 23))

#define FD2110_ALL_INTERRUPTS_DATA_MASK \
	((3 << 1) | (3 << 4) | (1 << 13) | (1 << 15) | (1 << 17) | (1 << 19))

#define FD2110_VDMA_RX_INTERRUPTS (0x0000FFFF)
#define FD2110_VDMA_TX_INTERRUPTS (0xFFFF0000)

#define FD2110_ALL_VDMA_INTERRUPTS (FD2110_VDMA_TX_INTERRUPTS | FD2110_VDMA_RX_INTERRUPTS)
#define FD2110_ALL_VDMA_INTERRUPTS_DATA_MASK (0)

#define FD2110_IO_IS_SDI(io) ((io) < 2)
#define FD2110_IO_VDMA_STREAM(io) (((io) - 2) % 16)
#define FD2110_IO_IS_TX(io) ((((io) - 2) % 32) >= 16)
#define FD2110_IO_ETHERNET(io) (((io) - 2) / 32)

#define FD2110_I2C_TIMEOUT_MS 10
#define FD2110_I2C_CYCLE_US 300

struct fd2110_udp_filter_entry {
	void *owner;
	u8 ip[16];
	u16 port;
	u8 stream;
	u8 tag;
};

static const struct fd2110_udp_filter_entry FILTER_MAX = {
	.owner = NULL,
	.ip = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
		0xff, 0xff },
	.port = 0xffff,
	.stream = 0,
	.tag = 0,
};

static const struct fd2110_udp_filter_entry FILTER_MIN = {
	.owner = NULL,
	.ip = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00 },
	.port = 0x0000,
	.stream = 0,
	.tag = 0,
};

struct fd2110_udp_filter {
	struct fd2110_udp_filter_entry table[64];
	int ucount, lcount;
	struct mutex lock;
};

struct fd2110_udp_mixer_entry {
	void *owner;
	u8 src_ip[16];
	u16 src_port;
	u8 dst_ip[16];
	u16 dst_port;
	u8 dst_mac[6];
	u8 ttl;
	u8 ipv6;
};

static const struct fd2110_udp_mixer_entry MIXER_ZERO = {
	.owner = NULL,
	.src_ip = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		    0x00, 0x00, 0x00 },
	.src_port = 0x0000,
	.dst_ip = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		    0x00, 0x00, 0x00 },
	.dst_port = 0x0000,
	.dst_mac = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
	.ttl = 0,
	.ipv6 = 0
};

struct fd2110_udp_mixer {
	const struct forward_fd2110_mixer_ops *ops;
	void *opsp;
	struct fd2110_udp_mixer_entry table[64];
	struct mutex lock;
};

struct fd2110_dev {
	struct forward_dev *dev;
	u32 ch_en[2];

	struct fd2110_udp_filter udp_in[2];
	struct fd2110_udp_mixer udp_out[2];
};

static inline int fd2110_filter_cmp(const struct fd2110_udp_filter_entry *e1,
				    const struct fd2110_udp_filter_entry *e2)
{
	int i;
	for (i = 0; i < 2; i++) {
		if (((u64 *)e1->ip)[i] < ((u64 *)e2->ip)[i])
			return -1;
		else if (((u64 *)e1->ip)[i] > ((u64 *)e2->ip)[i])
			return 1;
	}

	if (e1->port < e2->port)
		return -1;
	else if (e1->port > e2->port)
		return 1;

	return 0;
}

static void fd2110_in_filter_write(uint32_t *csr, int eth, int index,
				   const struct fd2110_udp_filter_entry *e)
{
	u32 *entryp = csr + 0x1000 + 0x1000 * eth + 0x200 + index * 8;
	int i;
	u32 dword;

	for (i = 0; i < 4; i++) {
		dword = *((u32 *)&e->ip[i * 4]);
		iowrite32(dword, entryp + i);
	}
	dword = (u32)e->port | ((u32)e->stream << 16) | ((u32)e->tag << 24);
	iowrite32(dword, entryp + 4);
}

static void fd2110_in_filter_init(struct forward_dev *dev, int eth)
{
	struct fd2110_dev *fd2110 = dev->cfg.private;
	int i;

	mutex_init(&fd2110->udp_in[eth].lock);

	for (i = 0; i < 32; i++) {
		fd2110->udp_in[eth].table[i] = FILTER_MIN;
		fd2110_in_filter_write(dev->csr, eth, i, &FILTER_MIN);
	}
	for (i = 32; i < 64; i++) {
		fd2110->udp_in[eth].table[i] = FILTER_MAX;
		fd2110_in_filter_write(dev->csr, eth, i, &FILTER_MAX);
	}
	fd2110->udp_in[eth].lcount = 0;
	fd2110->udp_in[eth].ucount = 0;
}

static void fd2110_in_filter_insert(struct fd2110_udp_filter *f, uint32_t *csr, int eth, int before,
				    const struct fd2110_udp_filter_entry *e)
{
	int it = before;

	if (((f->ucount > f->lcount) || (it == 64)) && (it != 0)) {
		// Move table up
		int prev = it - 1, cur;
		struct fd2110_udp_filter_entry ep = f->table[prev], tmp;
		for (cur = prev - 1; prev != 0; cur--) {
			fd2110_in_filter_write(csr, eth, cur, &ep);
			f->table[cur] = ep;
			ep = tmp;
			prev = cur;
		}
		it = it - 1;
		f->lcount++;
	} else {
		// Move table down
		int cur;
		struct fd2110_udp_filter_entry ep = f->table[it], tmp;
		for (cur = it + 1; cur != 64; cur++) {
			tmp = f->table[cur];
			fd2110_in_filter_write(csr, eth, cur, &ep);
			f->table[cur] = ep;
			ep = tmp;
		}
		f->ucount++;
	}
	fd2110_in_filter_write(csr, eth, it, e);
	f->table[it] = *e;
}

static void fd2110_in_filter_remove(struct fd2110_udp_filter *f, uint32_t *csr, int eth, int idx)
{
	if (idx < 32) {
		int cur;
		struct fd2110_udp_filter_entry ep, tmp;

		if (f->lcount <= 0)
			return;

		cur = 32 - f->lcount + 1;
		ep = f->table[cur - 1];

		for (; cur <= idx; cur++) {
			tmp = f->table[cur];
			fd2110_in_filter_write(csr, eth, cur, &ep);
			f->table[cur] = ep;
			ep = tmp;
		}
		fd2110_in_filter_write(csr, eth, 32 - f->lcount, &FILTER_MIN);
		f->table[32 - f->lcount] = FILTER_MIN;
		f->lcount--;
	} else {
		int cur;
		struct fd2110_udp_filter_entry ep, tmp;

		if (f->ucount <= 0)
			return;

		cur = 32 + f->ucount - 2;
		ep = f->table[cur + 1];

		for (; cur >= idx; cur--) {
			tmp = f->table[cur];
			fd2110_in_filter_write(csr, eth, cur, &ep);
			f->table[cur] = ep;
			ep = tmp;
		}
		fd2110_in_filter_write(csr, eth, 32 + f->ucount - 1, &FILTER_MAX);
		f->table[32 + f->ucount - 1] = FILTER_MAX;
		f->ucount--;
	}
}

int forward_fd2110_in_filter_add(struct forward_dev *dev, int eth, u8 stream, u8 tag,
				 const u8 *ipv6, u16 port, void *client)
{
	struct fd2110_dev *fd2110 = dev->cfg.private;
	struct fd2110_udp_filter *f = &fd2110->udp_in[eth];
	uint32_t *csr = dev->csr;
	int before, i;
	struct fd2110_udp_filter_entry e;

	for (i = 0; i < 16; i++)
		e.ip[i] = ipv6[15 - i];
	e.port = port;
	e.stream = stream;
	e.tag = tag;
	e.owner = client;

	mutex_lock(&f->lock);

	if ((f->ucount + f->lcount) >= 64) {
		mutex_unlock(&f->lock);
		return -ENOSPC;
	}

	for (before = 0; before < 64; before++) {
		if (fd2110_filter_cmp(&e, &f->table[before]) <= 0)
			break;
	}

	fd2110_in_filter_insert(f, csr, eth, before, &e);

	mutex_unlock(&f->lock);

	return 0;
}
EXPORT_SYMBOL(forward_fd2110_in_filter_add);

int forward_fd2110_in_filter_remove(struct forward_dev *dev, int eth, const u8 *ipv6, u16 port)
{
	struct fd2110_dev *fd2110 = dev->cfg.private;
	struct fd2110_udp_filter *f = &fd2110->udp_in[eth];
	uint32_t *csr = dev->csr;
	int idx, i;
	struct fd2110_udp_filter_entry e;

	for (i = 0; i < 16; i++)
		e.ip[i] = ipv6[15 - i];
	e.port = port;

	mutex_lock(&f->lock);

	for (idx = 0; idx < 64; idx++) {
		if (fd2110_filter_cmp(&e, &f->table[idx]) == 0)
			break;
	}

	if (idx >= 64) {
		mutex_unlock(&f->lock);
		return -ENOENT;
	}

	fd2110_in_filter_remove(f, csr, eth, idx);

	mutex_unlock(&f->lock);

	return 0;
}
EXPORT_SYMBOL(forward_fd2110_in_filter_remove);

void forward_fd2110_in_filter_remove_all(struct forward_dev *dev, int eth, void *client)
{
	struct fd2110_dev *fd2110 = dev->cfg.private;
	struct fd2110_udp_filter *f = &fd2110->udp_in[eth];
	uint32_t *csr = dev->csr;
	int idx;

	mutex_lock(&f->lock);

	do {
		for (idx = 0; idx < 64; idx++) {
			if (f->table[idx].owner == client)
				break;
		}

		if (idx >= 64)
			break;

		fd2110_in_filter_remove(f, csr, eth, idx);
	} while (idx < 64);

	mutex_unlock(&f->lock);
}
EXPORT_SYMBOL(forward_fd2110_in_filter_remove_all);

int forward_fd2110_in_filter_replace(struct forward_dev *dev, int eth, u8 stream, u8 tag,
				     const u8 *ipv6, u16 port, void *client)
{
	struct fd2110_dev *fd2110 = dev->cfg.private;
	struct fd2110_udp_filter *f = &fd2110->udp_in[eth];
	uint32_t *csr = dev->csr;
	int idx;

	mutex_lock(&f->lock);

	for (idx = 0; idx < 64; idx++) {
		struct fd2110_udp_filter_entry *e = &f->table[idx];
		if ((e->stream == stream) && (e->tag == tag) && (e->owner == client))
			break;
	}

	if (idx < 64)
		fd2110_in_filter_remove(f, csr, eth, idx);

	mutex_unlock(&f->lock);

	return forward_fd2110_in_filter_add(dev, eth, stream, tag, ipv6, port, client);
}
EXPORT_SYMBOL(forward_fd2110_in_filter_replace);

void forward_fd2110_in_filter_print(struct forward_dev *dev, int eth)
{
	struct fd2110_dev *fd2110 = dev->cfg.private;
	struct fd2110_udp_filter *f = &fd2110->udp_in[eth];
	int idx, i;

	for (idx = 0; idx < 64; idx++) {
		struct fd2110_udp_filter_entry *e = &f->table[idx];
		u8 ip[16];
		for (i = 0; i < 16; i++)
			ip[i] = e->ip[15 - i];
		forward_info(dev, "[%2d]: [%pI6]:%-5d %2d %2d %px", idx, ip, (int)e->port,
			     e->stream, e->tag, e->owner);
	}
}
EXPORT_SYMBOL(forward_fd2110_in_filter_print);

static void fd2110_out_mixer_write(uint32_t *csr, int eth, int index,
				   const struct fd2110_udp_mixer_entry *e)
{
	u32 *entryp = csr + 0x1000 + 0x1000 * eth + 0x800 + index * 16;
	int i;
	u32 dword;

	for (i = 0; i < 4; i++) {
		dword = *((u32 *)&e->src_ip[i * 4]);
		iowrite32(dword, entryp + 0 + i);
	}
	for (i = 0; i < 4; i++) {
		dword = *((u32 *)&e->dst_ip[i * 4]);
		iowrite32(dword, entryp + 4 + i);
	}

	dword = (u32)htons(e->src_port) | ((u32)htons(e->dst_port) << 16);
	iowrite32(dword, entryp + 8);

	dword = *((u32 *)e->dst_mac);
	iowrite32(dword, entryp + 9);

	dword = (u32)e->dst_mac[4] | ((u32)e->dst_mac[5] << 8) | ((u32)e->ttl << 16);
	iowrite32(dword, entryp + 10);

	dword = e->ipv6 ? 1 : 0;
	iowrite32(dword, entryp + 11);
}

static void fd2110_out_mixer_toggle(uint32_t *csr, int eth, int index, bool enabled)
{
	u32 *entryp = csr + 0x1000 + 0x1000 * eth + 0x800 + index * 16;
	iowrite32(enabled ? 0x1 : 0x0, entryp + 12);
}

static void fd2110_out_mixer_init(struct forward_dev *dev, int eth)
{
	struct fd2110_dev *fd2110 = dev->cfg.private;
	int i;

	mutex_init(&fd2110->udp_out[eth].lock);
	for (i = 0; i < 64; i++) {
		fd2110->udp_out[eth].table[i] = MIXER_ZERO;
		fd2110_out_mixer_toggle(dev->csr, eth, i, false);
		fd2110_out_mixer_write(dev->csr, eth, i, &MIXER_ZERO);
	}
}

int forward_fd2110_out_mixer_toggle_en(struct forward_dev *dev, int eth, u8 stream, bool enabled)
{
	if (stream >= 64)
		return -EINVAL;

	fd2110_out_mixer_toggle(dev->csr, eth, stream, enabled);

	return 0;
}
EXPORT_SYMBOL(forward_fd2110_out_mixer_toggle_en);

int forward_fd2110_out_mixer_update(struct forward_dev *dev, int eth, u8 stream, u16 src_port,
				    const u8 *dst_ipv6, u16 dst_port, bool ipv6, void *client)
{
	struct fd2110_dev *fd2110 = dev->cfg.private;
	struct fd2110_udp_mixer *m = &fd2110->udp_out[eth];
	struct fd2110_udp_mixer_entry e;
	int i;
	bool mac_valid = false;
	bool src_ip_valid = false;

	if (stream >= 64)
		return -EINVAL;

	e.owner = client;
	e.src_port = src_port;
	for (i = 0; i < 16; i++)
		e.dst_ip[i] = dst_ipv6[i];
	e.dst_port = dst_port;
	e.ipv6 = ipv6;

	if (m->ops && m->ops->get_dst) {
		if (!m->ops->get_dst(m->opsp, ipv6, e.dst_ip, e.dst_mac, &e.ttl))
			memset(e.dst_mac, 0, sizeof(e.dst_mac));
		else
			mac_valid = true;
	}

	if (m->ops && m->ops->get_src) {
		if (!m->ops->get_src(m->opsp, ipv6, e.src_ip))
			memset(e.src_ip, 0, sizeof(e.src_ip));
		else
			src_ip_valid = true;
	}

	mutex_lock(&m->lock);
	m->table[stream] = e;
	fd2110_out_mixer_write(dev->csr, eth, stream, &e);
	mutex_unlock(&m->lock);

	return 0;
}
EXPORT_SYMBOL(forward_fd2110_out_mixer_update);

void forward_fd2110_out_mixer_disable_all(struct forward_dev *dev, int eth, void *client)
{
	struct fd2110_dev *fd2110 = dev->cfg.private;
	struct fd2110_udp_mixer *m = &fd2110->udp_out[eth];
	int idx;

	for (idx = 0; idx < 64; idx++) {
		if (m->table[idx].owner == client)
			forward_fd2110_out_mixer_toggle_en(dev, eth, idx, false);
	}
}
EXPORT_SYMBOL(forward_fd2110_out_mixer_disable_all);

void forward_fd2110_out_mixer_print(struct forward_dev *dev, int eth)
{
	struct fd2110_dev *fd2110 = dev->cfg.private;
	struct fd2110_udp_mixer *m = &fd2110->udp_out[eth];
	int idx;

	for (idx = 0; idx < 64; idx++) {
		struct fd2110_udp_mixer_entry *e = &m->table[idx];
		forward_info(dev, "[%2d]: [%pI6]:%-5d -> [%pI6]:%-5d (%pM) %2d %d %px", idx,
			     e->src_ip, (int)e->src_port, e->dst_ip, (int)e->dst_port, e->dst_mac,
			     (int)e->ttl, (int)e->ipv6, e->owner);
	}
}
EXPORT_SYMBOL(forward_fd2110_out_mixer_print);

void forward_fd2110_out_mixer_set_ops(struct forward_dev *dev, int eth,
				      const struct forward_fd2110_mixer_ops *ops, void *private)
{
	struct fd2110_dev *fd2110 = dev->cfg.private;
	struct fd2110_udp_mixer *m = &fd2110->udp_out[eth];

	m->opsp = private;
	m->ops = ops;
}
EXPORT_SYMBOL(forward_fd2110_out_mixer_set_ops);

static int fd2110_check_firmware(struct fd2110_dev *fd2110)
{
	struct forward_dev *dev = fd2110->dev;
	const struct firmware *fw = NULL;
	char fw_name[64];
	int result;

	if (!dev->mcap_addr) {
		dev_warn(dev->parent_dev, "MCAP not found, assume recovery/debug firmware\n");
		return 0;
	}

	dev_info(dev->parent_dev, "Static firmware version: 0x%08x\n", dev->mcap_fw_version);
	if (!dev->mcap_fw_version) {
		dev_warn(dev->parent_dev,
			 "No static firmware version, assume recovery/debug firmware\n");
		return 0;
	}

	snprintf(fw_name, sizeof(fw_name), "forward/fd2110v%d_%d_%d.bin",
		 (dev->mcap_fw_version >> 26) & 0x7, (dev->mcap_fw_version >> 24) & 0x3,
		 (dev->mcap_fw_version >> 0) & 0xFFFF);

	dev_info(dev->parent_dev, "Requesting dynamic firmware: %s\n", fw_name);
	result = request_firmware(&fw, fw_name, dev->parent_dev);

	if (result) {
		dev_warn(dev->parent_dev,
			 "Dynamic firmware not found, rebooting device to recovery firmware\n");
		dev_warn(
			dev->parent_dev,
			"Check dynamic firmware file exist or/and update static firmware on flash\n");
		return forward_mcap_reboot(fd2110->dev, 0x00000000, true);
	}

	result = forward_mcap_flash_firmware(fd2110->dev, fw);

	if (fw)
		release_firmware(fw);

	return result;
}

static void fd2110_reboot(struct forward_dev *dev)
{
	struct fd2110_dev *fd2110 = (struct fd2110_dev *)dev->cfg.private;
	int result;

	result = forward_mcap_reboot(fd2110->dev, 0x01000000, false);
	if (result)
		dev_warn(fd2110->dev->parent_dev, "MCAP reboot failed: %d\n", result);

	if (!result)
		fd2110_check_firmware(fd2110);
}

static bool fd2110_spi_xfer(struct forward_dev *dev, u8 bus, u8 *tx, int tx_bits, u8 *rx,
			    int rx_bits)
{
	int bits = max(tx_bits, rx_bits);
	int word_count = (bits - 1) / 32 + 1;
	int byte_count = (bits - 1) / 8 + 1;
	int last_word_bits = bits % 32;

	FD2110_SPIControl ctl;
	FD2110_SPIData data;
	int i;

	ctl.bus = bus + 1;
	ctl.rxBits = rx_bits;
	ctl.txBits = tx_bits;
	FD2110_SPIControl_W(dev->csr, ctl);

	for (i = 0; i < word_count; i++) {
		int j;

		if ((i * 8) < tx_bits) {
			u32 value = 0;
			if (tx) {
				for (j = 0; j < 4; j++) {
					if (i * 4 + j < byte_count)
						value |= tx[i * 4 + j] << ((3 - j) * 8);
				}
			} else
				value = 0xFFFFFFFF;
			data.data = value;
			FD2110_SPIData_W(dev->csr, data);
		}

		if ((i * 8) < rx_bits) {
			data = FD2110_SPIData_R(dev->csr);
			if (i == (word_count - 1))
				data.data <<= (32 - last_word_bits) % 32;

			if (rx) {
				for (j = 0; j < 4; j++) {
					if (i * 4 + j < byte_count)
						rx[i * 4 + j] = (data.data >> ((3 - j) * 8)) & 0xFF;
				}
			}
		}
	}

	ctl.bus = 0;
	ctl.rxBits = 0;
	ctl.txBits = 0;
	FD2110_SPIControl_W(dev->csr, ctl);

	return true;
}

static bool fd2110_i2c_xfer(struct forward_dev *dev, u8 bus, bool read, uint8_t addr,
			    uint8_t subaddr, uint8_t *data)
{
	FD2110_I2C i2c = { 0 };
	unsigned long timeout;

	i2c.cs = 1;
	i2c.rw = read ? 1 : 0;
	i2c.address = addr;
	i2c.subAddress = subaddr;
	i2c.data = *data;
	i2c.bus = bus;

	FD2110_I2C_W(dev->csr, i2c);

	timeout = jiffies + (HZ * FD2110_I2C_TIMEOUT_MS) / 1000;

	do {
		usleep_range(FD2110_I2C_CYCLE_US, FD2110_I2C_CYCLE_US * 2);
		i2c = FD2110_I2C_R(dev->csr);
	} while (time_before(jiffies, timeout) && i2c.cs);

	if (read && !i2c.cs && !i2c.nack)
		*data = i2c.data;

	return !i2c.cs && !i2c.nack;
}

static int fd2110_init(struct forward_dev *dev)
{
	int result = 0;
	struct fd2110_dev *fd2110 =
		devm_kzalloc(dev->parent_dev, sizeof(struct fd2110_dev), GFP_KERNEL);
	ktime_t sys_time, hw_time;
	s64 time_delta;

	if (!fd2110)
		return -ENOMEM;

	fd2110->dev = dev;
	dev->cfg.private = fd2110;
	dev->spi_xfer = &fd2110_spi_xfer;
	dev->i2c_xfer = &fd2110_i2c_xfer;

	result = fd2110_check_firmware(fd2110);

	if (result)
		return result;

	forward_lmk05028_init(dev, &dev->pll, 1, 1, 3);

	sys_time = ktime_get_clocktai();
	hw_time = ioread32(&dev->csr[FD2110_TimeCounterNanoseconds_A]);
	hw_time |= (u64)ioread32(&dev->csr[FD2110_TimeCounterSecondsL_A]) * NSEC_PER_SEC;
	hw_time |= ((u64)ioread32(&dev->csr[FD2110_TimeCounterSecondsH_A]) << 32) * NSEC_PER_SEC;
	time_delta = ktime_sub(sys_time, hw_time);
	if (abs(time_delta) > 10000000000ULL) {
		s64 ds = time_delta / 1000000000LL;
		s32 dns = time_delta - (ds * 1000000000LL);
		if (dns > 500000000) {
			ds += 1;
			dns -= 1000000000;
		} else if (dns < -500000000) {
			ds -= 1;
			dns += 1000000000;
		}
		iowrite32((ds >> 32) & 0xFFFFFFFF, &dev->csr[FD2110_TimeCounterSecondsH_A]);
		iowrite32(ds & 0xFFFFFFFF, &dev->csr[FD2110_TimeCounterSecondsL_A]);
		iowrite32(dns & 0xFFFFFFFF, &dev->csr[FD2110_TimeCounterNanoseconds_A]);
	}

	fd2110_in_filter_init(dev, 0);
	fd2110_in_filter_init(dev, 1);
	fd2110_out_mixer_init(dev, 0);
	fd2110_out_mixer_init(dev, 1);

	return result;
}

static void fd2110_fini(struct forward_dev *dev)
{
	struct fd2110_dev *fd2110 = dev->cfg.private;

	forward_lmk05028_fini(dev, &dev->pll);

	if (fd2110)
		devm_kfree(dev->parent_dev, fd2110);
}

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

	FD2110_SoftID id = FD2110_SoftID_R(dev->csr);
	FD2110_StaticVersion v = FD2110_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 = *((uint32_t *)&v);
}

static void fd2110_enable_interrupts(struct forward_dev *dev, const forward_irq_t *interrupts)
{
	u32 irqs[BITS_TO_U32(FORWARD_IRQ_FLAG_BITS)];
	u32 hw_irq;

	forward_irq_flags_unpack(irqs, *interrupts);

	hw_irq = ioread32(&dev->csr[FD2110_IRQEnable_A]);
	hw_irq |= irqs[0] & FD2110_ALL_INTERRUPTS;
	iowrite32(hw_irq, &dev->csr[FD2110_IRQEnable_A]);

	hw_irq = ioread32(&dev->csr[FD2110_VDMAIRQEnable_A(0)]);
	hw_irq |= irqs[1] & FD2110_ALL_VDMA_INTERRUPTS;
	iowrite32(hw_irq, &dev->csr[FD2110_VDMAIRQEnable_A(0)]);

	hw_irq = ioread32(&dev->csr[FD2110_VDMAIRQEnable_A(1)]);
	hw_irq |= irqs[2] & FD2110_ALL_VDMA_INTERRUPTS;
	iowrite32(hw_irq, &dev->csr[FD2110_VDMAIRQEnable_A(1)]);
}

static void fd2110_disable_interrupts(struct forward_dev *dev, const forward_irq_t *interrupts)
{
	u32 irqs[BITS_TO_U32(FORWARD_IRQ_FLAG_BITS)];
	u32 hw_irq;

	forward_irq_flags_unpack(irqs, *interrupts);

	hw_irq = ioread32(&dev->csr[FD2110_IRQEnable_A]);
	hw_irq &= ~(irqs[0] & FD2110_ALL_INTERRUPTS);
	iowrite32(hw_irq, &dev->csr[FD2110_IRQEnable_A]);

	hw_irq = ioread32(&dev->csr[FD2110_VDMAIRQEnable_A(0)]);
	hw_irq &= ~(irqs[1] & FD2110_ALL_VDMA_INTERRUPTS);
	iowrite32(hw_irq, &dev->csr[FD2110_VDMAIRQEnable_A(0)]);

	hw_irq = ioread32(&dev->csr[FD2110_VDMAIRQEnable_A(1)]);
	hw_irq &= ~(irqs[2] & FD2110_ALL_VDMA_INTERRUPTS);
	iowrite32(hw_irq, &dev->csr[FD2110_VDMAIRQEnable_A(1)]);
}

static bool fd2110_read_interrupts(struct forward_dev *dev, forward_irq_t *interrupts,
				   forward_irq_t *data, forward_irq_t *data_mask)
{
	u32 irq[BITS_TO_U32(FORWARD_IRQ_FLAG_BITS)] = { 0 };
	u32 mask[BITS_TO_U32(FORWARD_IRQ_FLAG_BITS)] = { 0 };
	u32 dmask[BITS_TO_U32(FORWARD_IRQ_FLAG_BITS)] = { 0 };
	forward_irq_t irq_mask;

	irq[0] = ioread32(&dev->csr[FD2110_IRQFlags_A]);
	iowrite32(irq[0], &dev->csr[FD2110_IRQFlags_A]);
	irq[1] = ioread32(&dev->csr[FD2110_VDMAIRQFlags_A(0)]);
	iowrite32(irq[1], &dev->csr[FD2110_VDMAIRQFlags_A(0)]);
	irq[2] = ioread32(&dev->csr[FD2110_VDMAIRQFlags_A(1)]);
	iowrite32(irq[2], &dev->csr[FD2110_VDMAIRQFlags_A(1)]);

	mask[0] = FD2110_ALL_INTERRUPTS;
	mask[1] = FD2110_ALL_VDMA_INTERRUPTS;
	mask[2] = FD2110_ALL_VDMA_INTERRUPTS;

	dmask[0] = FD2110_ALL_INTERRUPTS_DATA_MASK;
	dmask[1] = FD2110_ALL_VDMA_INTERRUPTS_DATA_MASK;
	dmask[2] = FD2110_ALL_VDMA_INTERRUPTS_DATA_MASK;

	forward_irq_flags_pack(irq_mask, mask);
	forward_irq_flags_pack(*interrupts, irq);
	forward_irq_flags_pack(*data, irq);
	forward_irq_flags_pack(*data_mask, dmask);

	forward_irq_flags_and(*interrupts, *interrupts, irq_mask);
	forward_irq_flags_and(*data, *interrupts, *data_mask);

	return !forward_irq_flags_is_zero(*interrupts);
}

static void fd2110_enable_vdma(struct forward_dev *dev, u32 map_addr)
{
	FD2110_VDMADescriptor desc = { .address = map_addr };
	FD2110_VDMA vdma = { .enable = 1 };
	FD2110_VDMADescriptor_W(dev->csr, desc);
	FD2110_VDMA_W(dev->csr, vdma);
}

static void fd2110_disable_vdma(struct forward_dev *dev)
{
	FD2110_VDMA vdma = { .enable = 0 };
	FD2110_VDMA_W(dev->csr, vdma);
}

static enum forward_io_type fd2110_io_type(struct forward_dev *dev, int io)
{
	if (FD2110_IO_IS_SDI(io))
		return FORWARD_IO_BIDIR;
	else
		return FD2110_IO_IS_TX(io) ? FORWARD_IO_OUTPUT : FORWARD_IO_INPUT;
}

static int fd2110_io_number(struct forward_dev *dev, int io)
{
	if (FD2110_IO_IS_SDI(io))
		return io + 1;
	else
		return FD2110_IO_VDMA_STREAM(io);
}

static enum forward_io_state fd2110_io_state(struct forward_dev *dev, int io)
{
	struct fd2110_dev *fd2110 = (struct fd2110_dev *)dev->cfg.private;
	if (FD2110_IO_IS_SDI(io)) {
		FD2110_VideoConfig r = FD2110_VideoConfig_R(dev->csr);
		uint32_t cfg = *((uint32_t *)&r);

		if (cfg & (1 << io)) {
			FD2110_VideoOutCS cs = FD2110_VideoOutCS_R(dev->csr, io);
			return cs.reset ? FORWARD_IO_DISABLED : FORWARD_IO_TX;
		} else {
			return FORWARD_IO_RX;
		}
	} else {
		if (!(fd2110->ch_en[FD2110_IO_ETHERNET(io)] & (1 << FD2110_IO_VDMA_STREAM(io))))
			return FORWARD_IO_DISABLED;
		else
			return FD2110_IO_IS_TX(io) ? FORWARD_IO_TX : FORWARD_IO_RX;
	}
}

static void fd2110_toggle_streaming(struct forward_dev *dev, int io, bool enabled)
{
	if (FD2110_IO_IS_SDI(io)) {
	} else if (FD2110_IO_IS_TX(io)) {
		FD2110_VDMATxVideoControl ctl = FD2110_VDMATxVideoControl_R(
			dev->csr, FD2110_IO_ETHERNET(io), FD2110_IO_VDMA_STREAM(io));
		ctl.enable = enabled ? 1 : 0;
		FD2110_VDMATxVideoControl_W(dev->csr, FD2110_IO_ETHERNET(io),
					    FD2110_IO_VDMA_STREAM(io), ctl);
	} else {
		FD2110_VDMARxVideoControl ctl = FD2110_VDMARxVideoControl_R(
			dev->csr, FD2110_IO_ETHERNET(io), FD2110_IO_VDMA_STREAM(io));
		ctl.enable = enabled ? 1 : 0;
		FD2110_VDMARxVideoControl_W(dev->csr, FD2110_IO_ETHERNET(io),
					    FD2110_IO_VDMA_STREAM(io), ctl);
	}
}

static int fd2110_set_io_state(struct forward_dev *dev, int io, enum forward_io_state state)
{
	struct fd2110_dev *fd2110 = (struct fd2110_dev *)dev->cfg.private;
	if (FD2110_IO_IS_SDI(io)) {
		FD2110_VideoConfig r = FD2110_VideoConfig_R(dev->csr);
		uint32_t cfg = *((uint32_t *)&r);

		if (state == FORWARD_IO_TXRX)
			return -EOPNOTSUPP;

		if (state == FORWARD_IO_TX)
			cfg |= (1 << io);
		else
			cfg &= ~(1 << io);

		r = *((FD2110_VideoConfig *)&cfg);
		FD2110_VideoConfig_W(dev->csr, r);

		if (cfg & (1 << io)) {
			FD2110_VideoOutCS cs = FD2110_VideoOutCS_R(dev->csr, io);
			cs.reset = state == FORWARD_IO_DISABLED ? 1 : 0;
			FD2110_VideoOutCS_W(dev->csr, io, cs);
		}
	} else {
		if (state == FORWARD_IO_TXRX)
			return -EOPNOTSUPP;

		if (state == FORWARD_IO_DISABLED) {
			fd2110_toggle_streaming(dev, io, false);
			fd2110->ch_en[FD2110_IO_ETHERNET(io)] &= ~(1 << FD2110_IO_VDMA_STREAM(io));
		} else if (state == FORWARD_IO_RX) {
			if (FD2110_IO_IS_TX(io))
				return -EINVAL;

			fd2110->ch_en[FD2110_IO_ETHERNET(io)] |= 1 << FD2110_IO_VDMA_STREAM(io);
		} else if (state == FORWARD_IO_TX) {
			if (!FD2110_IO_IS_TX(io))
				return -EINVAL;

			fd2110->ch_en[FD2110_IO_ETHERNET(io)] |= 1 << FD2110_IO_VDMA_STREAM(io);
		}
	}

	return 0;
}

static void fd2110_enable_sdma(struct forward_dev *dev, u32 map_addr)
{
	FD2110_SDMAControl ctl = { 0 };
	FD2110_SDMAPage p;

	p.address = map_addr;
	FD2110_SDMAPage_W(dev->csr, p);

	ctl.enable = 1;
	FD2110_SDMAControl_W(dev->csr, ctl);
}

static void fd2110_disable_sdma(struct forward_dev *dev)
{
	FD2110_SDMAControl ctl;

	ctl.enable = 0;
	FD2110_SDMAControl_W(dev->csr, ctl);
}

static void fd2110_sdma_enable_ring(struct forward_dev *dev, int ring)
{
	FD2110_SDMAControl ctl = FD2110_SDMAControl_R(dev->csr);

	switch (ring) {
	case 0:
		ctl.enableRX0 = 1;
		break;
	case 1:
		ctl.enableRX1 = 1;
		break;
	case 2:
		ctl.enableTX0 = 1;
		break;
	case 3:
		ctl.enableTX1 = 1;
		break;
	}

	FD2110_SDMAControl_W(dev->csr, ctl);
}

static void fd2110_sdma_disable_ring(struct forward_dev *dev, int ring)
{
	FD2110_SDMAControl ctl = FD2110_SDMAControl_R(dev->csr);

	switch (ring) {
	case 0:
		ctl.enableRX0 = 0;
		break;
	case 1:
		ctl.enableRX1 = 0;
		break;
	case 2:
		ctl.enableTX0 = 0;
		break;
	case 3:
		ctl.enableTX1 = 0;
		break;
	}

	FD2110_SDMAControl_W(dev->csr, ctl);
}

static void fd2110_sdma_get_hw_pointer(struct forward_dev *dev, int ring, u16 *hw)
{
	FD2110_SDMAPointer p = FD2110_SDMAPointer_R(dev->csr, ring);
	*hw = p.hwPointer;
}

static void fd2110_sdma_set_pointers(struct forward_dev *dev, int ring, u16 hw, bool update_hw,
				     u16 sw, bool update_sw)
{
	FD2110_SDMAPointer p;
	p.hwPointer = hw;
	p.swPointer = sw;
	p.hwUpdate = update_hw ? 1 : 0;
	p.swUpdate = update_sw ? 1 : 0;
	FD2110_SDMAPointer_W(dev->csr, ring, p);
}

static ssize_t fd2110_attr_show_adc(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t fd2110_attr_show_version(struct device *dev, struct device_attribute *attr,
					char *buf);
static ssize_t fd2110_attr_show_hw_version(struct device *dev, struct device_attribute *attr,
					   char *buf);

static DEVICE_ATTR(temperature, S_IRUGO, fd2110_attr_show_adc, NULL);
static DEVICE_ATTR(vcore, S_IRUGO, fd2110_attr_show_adc, NULL);
static DEVICE_ATTR(vaux, S_IRUGO, fd2110_attr_show_adc, NULL);
static DEVICE_ATTR(version, S_IRUGO, fd2110_attr_show_version, NULL);
static DEVICE_ATTR(hw_version, S_IRUGO, fd2110_attr_show_hw_version, NULL);

static ssize_t fd2110_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) {
		FD2110_TemperatureMonitor temp = FD2110_TemperatureMonitor_R(fdev->csr);
		int value = temp.temperature * 509314 / 1024 - 280231;
		dec = value / 1000;
		frac = abs(value % 1000);
	} else if (attr == &dev_attr_vcore) {
		FD2110_VCoreMonitor v = FD2110_VCoreMonitor_R(fdev->csr);
		int value = v.voltage * 3 * 1000 / 1024;
		dec = value / 1000;
		frac = value % 1000;
	} else if (attr == &dev_attr_vaux) {
		FD2110_VAuxMonitor v = FD2110_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 fd2110_attr_show_version(struct device *dev, struct device_attribute *attr,
					char *buf)
{
	struct forward_dev *fdev = dev_get_drvdata(dev);
	FD2110_StaticVersion version = FD2110_StaticVersion_R(fdev->csr);

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

static ssize_t fd2110_attr_show_hw_version(struct device *dev, struct device_attribute *attr,
					   char *buf)
{
	struct forward_dev *fdev = dev_get_drvdata(dev);
	FD2110_StaticVersion version = FD2110_StaticVersion_R(fdev->csr);

	return scnprintf(buf, PAGE_SIZE, "%d.%d\n", version.hwRevision, version.hwSubRevision);
}

static struct attribute *fd2110_dev_attrs[] = {
	&dev_attr_temperature.attr, &dev_attr_vcore.attr,      &dev_attr_vaux.attr,
	&dev_attr_version.attr,	    &dev_attr_hw_version.attr, NULL,
};

static struct attribute_group fd2110_attr_group = {
	.attrs = fd2110_dev_attrs,
};

struct forward_dev_config fd2110_cfg = {
	.vdma_size_mb = 4096,
	.sdma_size = 4 * 32768,
	.sdma_rings = 4,
	.io_count = 2 + (16 + 16) * 2,

	.attributes = &fd2110_attr_group,

	.private = NULL,

	.init = fd2110_init,
	.fini = fd2110_fini,
	.reboot = fd2110_reboot,

	.read_id = fd2110_read_id,

	.enable_interrupts = fd2110_enable_interrupts,
	.disable_interrupts = fd2110_disable_interrupts,
	.read_interrupts = fd2110_read_interrupts,

	.enable_vdma = fd2110_enable_vdma,
	.disable_vdma = fd2110_disable_vdma,

	.io_type = fd2110_io_type,
	.io_number = fd2110_io_number,
	.io_state = fd2110_io_state,
	.set_io_state = fd2110_set_io_state,

	.toggle_streaming = fd2110_toggle_streaming,

	.enable_sdma = fd2110_enable_sdma,
	.disable_sdma = fd2110_disable_sdma,
	.sdma_enable_ring = fd2110_sdma_enable_ring,
	.sdma_disable_ring = fd2110_sdma_disable_ring,
	.sdma_get_hw_pointer = fd2110_sdma_get_hw_pointer,
	.sdma_set_pointers = fd2110_sdma_set_pointers,
};
