/*
   forward-mcap.c - PCIe configuration port driver for SoftLab-NSK Forward boards

   Copyright (C) 2017 - 2025 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-mcap.h"

#include <linux/delay.h>

#define MCAP_EXT_CAP_HEADER 0x00
#define MCAP_VEND_SPEC_HEADER 0x04
#define MCAP_FPGA_JTAG_ID 0x08
#define MCAP_FPGA_BIT_VERSION 0x0C
#define MCAP_STATUS 0x10
#define MCAP_CONTROL 0x14
#define MCAP_DATA 0x18
#define MCAP_READ_DATA_0 0x1C
#define MCAP_READ_DATA_1 0x20
#define MCAP_READ_DATA_2 0x24
#define MCAP_READ_DATA_3 0x28

#define MCAP_CTRL_ENABLE (1 << 0)
#define MCAP_CTRL_READ_ENABLE (1 << 1)
#define MCAP_CTRL_RESET (1 << 4)
#define MCAP_CTRL_MOD_RESET (1 << 5)
#define MCAP_CTRL_REQUEST (1 << 8)
#define MCAP_CTRL_DESIGN_SWITCH (1 << 12)
#define MCAP_CTRL_WR_DATA_EN (1 << 16)

#define MCAP_STS_ERROR (1 << 0)
#define MCAP_STS_EOS (1 << 1)
#define MCAP_STS_READ_CMP (1 << 4)
#define MCAP_STS_READ_COUNT (7 << 5)
#define MCAP_STS_FIFO_OVERFLOW (1 << 8)
#define MCAP_STS_FIFO_OCCUPANCY (15 << 12)
#define MCAP_STS_RELEASE (1 << 24)

inline int mcap_read_reg(struct forward_dev *dev, u8 reg, u32 *value)
{
	return pci_read_config_dword(dev->pci_dev, dev->mcap_addr + reg, value);
}

inline int mcap_write_reg(struct forward_dev *dev, u8 reg, u32 value)
{
	return pci_write_config_dword(dev->pci_dev, dev->mcap_addr + reg, value);
}

inline int mcap_write_reg_mask(struct forward_dev *dev, u8 reg, u32 value, u32 mask)
{
	u32 v;
	int status;

	status = mcap_read_reg(dev, reg, &v);
	if (status)
		return status;

	v = (v & (~mask)) | (value & mask);

	status = mcap_write_reg(dev, reg, v);
	return status;
}

static int mcap_request(struct forward_dev *dev)
{
	unsigned long timeout;
	u32 status;
	int result = 0;

	result = mcap_write_reg_mask(dev, MCAP_CONTROL, MCAP_CTRL_ENABLE | MCAP_CTRL_REQUEST,
				     MCAP_CTRL_ENABLE | MCAP_CTRL_REQUEST);

	if (result)
		return result;

	timeout = jiffies + HZ;

	do {
		msleep(1);
		result = mcap_read_reg(dev, MCAP_STATUS, &status);
	} while (!result && (status & MCAP_STS_RELEASE) && time_before(jiffies, timeout));

	if (result)
		return result;

	if (!time_before(jiffies, timeout)) {
		result = -ETIMEDOUT;
		return result;
	}

	return 0;
}

static int mcap_reset(struct forward_dev *dev)
{
	int status = 0;

	status = mcap_write_reg_mask(dev, MCAP_CONTROL, MCAP_CTRL_RESET | MCAP_CTRL_MOD_RESET,
				     MCAP_CTRL_RESET | MCAP_CTRL_MOD_RESET);
	if (status)
		return status;

	status = mcap_write_reg_mask(dev, MCAP_CONTROL, 0, MCAP_CTRL_RESET | MCAP_CTRL_MOD_RESET);
	return status;
}

static int mcap_release(struct forward_dev *dev)
{
	return mcap_write_reg_mask(dev, MCAP_CONTROL, 0, MCAP_CTRL_ENABLE | MCAP_CTRL_REQUEST);
}

static int mcap_design_switch(struct forward_dev *dev, bool on)
{
	return mcap_write_reg_mask(dev, MCAP_CONTROL, on ? MCAP_CTRL_DESIGN_SWITCH : 0,
				   MCAP_CTRL_DESIGN_SWITCH);
}

static int mcap_write_bitstream(struct forward_dev *dev, const struct firmware *fw)
{
	int result = 0;
	size_t i;
	unsigned long timeout;
	u32 status;

	result = mcap_write_reg_mask(dev, MCAP_CONTROL, MCAP_CTRL_WR_DATA_EN,
				     MCAP_CTRL_WR_DATA_EN | MCAP_CTRL_READ_ENABLE);
	if (result)
		return result;

	for (i = 0; i < fw->size / 4; i++) {
		u32 data = cpu_to_be32(((u32 *)fw->data)[i]);
		result = mcap_write_reg(dev, MCAP_DATA, data);
		if (result)
			return result;
	}

	timeout = jiffies + HZ;

	do {
		msleep(1);
		result = mcap_read_reg(dev, MCAP_STATUS, &status);
	} while (!result && !(status & MCAP_STS_EOS) && time_before(jiffies, timeout));

	if (!time_before(jiffies, timeout)) {
		result = mcap_write_reg_mask(dev, MCAP_CONTROL, 0,
					     MCAP_CTRL_WR_DATA_EN | MCAP_CTRL_READ_ENABLE);
		return -ETIMEDOUT;
	}

	return result;
}

int forward_mcap_flash_firmware(struct forward_dev *dev, const struct firmware *fw)
{
	int result = 0;

	result = mcap_request(dev);
	if (result) {
		dev_warn(dev->parent_dev, "MCAP request failed: %d\n", result);
		goto end;
	}

	result = mcap_reset(dev);
	if (result) {
		dev_warn(dev->parent_dev, "MCAP reset failed: %d\n", result);
		goto end;
	}

	result = mcap_design_switch(dev, false);
	if (result) {
		dev_warn(dev->parent_dev, "MCAP design siwtch off failed: %d\n", result);
		goto end;
	}

	result = mcap_write_bitstream(dev, fw);
	if (result) {
		dev_warn(dev->parent_dev, "MCAP bitstream write failed: %d\n", result);
		goto end;
	}

	result = mcap_design_switch(dev, true);
	if (result) {
		dev_warn(dev->parent_dev, "MCAP design siwtch on failed: %d\n", result);
		goto end;
	}

end:
	mcap_release(dev);

	return result;
}

int forward_mcap_reboot(struct forward_dev *dev, u32 address, bool fallback)
{
	int result = 0;

	if (!dev->mcap_addr)
		return -ENODEV;

	result = mcap_write_reg_mask(dev, MCAP_CONTROL, MCAP_CTRL_WR_DATA_EN,
				     MCAP_CTRL_WR_DATA_EN | MCAP_CTRL_READ_ENABLE);
	if (result)
		return result;

	result = mcap_request(dev);
	if (result) {
		dev_warn(dev->parent_dev, "MCAP request failed: %d\n", result);
		goto end;
	}

	result = mcap_reset(dev);
	if (result) {
		dev_warn(dev->parent_dev, "MCAP reset failed: %d\n", result);
		goto end;
	}

	result = mcap_design_switch(dev, false);
	if (result) {
		dev_warn(dev->parent_dev, "MCAP design siwtch off failed: %d\n", result);
		goto end;
	}

	pci_clear_master(dev->pci_dev);
	pci_save_state(dev->pci_dev);

	mcap_write_reg(dev, MCAP_DATA, 0xFFFFFFFF);
	mcap_write_reg(dev, MCAP_DATA, 0xAA995566);
	mcap_write_reg(dev, MCAP_DATA, 0x20000000);
	mcap_write_reg(dev, MCAP_DATA, 0x30022001);
	mcap_write_reg(dev, MCAP_DATA, fallback ? 0x40000100 : 0x00000000);
	mcap_write_reg(dev, MCAP_DATA, 0x20000000);
	mcap_write_reg(dev, MCAP_DATA, 0x30020001);
	mcap_write_reg(dev, MCAP_DATA, address);
	mcap_write_reg(dev, MCAP_DATA, 0x20000000);
	mcap_write_reg(dev, MCAP_DATA, 0x30008001);
	mcap_write_reg(dev, MCAP_DATA, 0x0000000F);
	mcap_write_reg(dev, MCAP_DATA, 0x20000000);

	msleep(1000);

	pci_restore_state(dev->pci_dev);
	pci_set_master(dev->pci_dev);

end:
	mcap_release(dev);

	return result;
}

int forward_mcap_init(struct forward_dev *dev)
{
	dev->mcap_addr = pci_find_ext_capability(dev->pci_dev, PCI_EXT_CAP_ID_VNDR);

	if (!dev->mcap_addr)
		return -ENODEV;

	mcap_read_reg(dev, MCAP_FPGA_BIT_VERSION, &dev->mcap_fw_version);

	return 0;
}

void forward_mcap_fini(struct forward_dev *dev)
{
	dev->mcap_addr = 0;
	dev->mcap_fw_version = 0;
}
