// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2022-2024, NVIDIA CORPORATION.  All rights reserved.

#include <nvidia/conftest.h>

#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/spi/spi.h>
//#include <soc/tegra/virt/tegra_hv_pm_ctl.h>
#include <linux/sched/signal.h>

#define AURIX			0x3
#define TEGRA			0x2
#define SRC(x)			((x))
#define DEST(x)			((x) << 4)
#define FUSA			0x2
#define SHUTDOWN		0x6
#define RESET			0x7
#define SUSPEND			0x8
#define CMD_SHIFT(x)		((x) << 3)
#define AURIX_TEGRA		0x0
#define TEGRA_AURIX		0x1
#define REQ_TYPE(x)		((x) << 7)
#define SPI_DEBUG		1

/*
 * Command-Response packet shall follow the structure shown below.
 * Packet contains information (e.g. class id, rsp flag) that
 * requires 4, 3 or 1 bit. Using Bit Field since we need less than
 * 1 byte memory.
 */
struct packet {
	u8 e2e_crc;
	u8 e2e_alive_counter:4;
	u8 data_id_nibble:4;
	u8 src_layer_id:4;
	u8 dest_layer_id:4;
	u8 class_id:3;
	u8 cmd_msg_id:4;
	u8 rsp_flag:1;
	u8 msg_spec_payload[12];
} __attribute__((__packed__));

struct aurix_tegra_spi_data {
	struct spi_device *spi;
	struct task_struct *thread;
	struct packet *transmit;
	struct packet *receive;
	spinlock_t lock;
	bool exited;
	u8 buf_len;
	u8 command;
};


/***************TBD ** use actual APIs from soc/tegra/virt/tegra_hv_pm_ctl.h ***/
static int tegra_hv_pm_ctl_trigger_sys_suspend(void)
{
	return -ENOTSUPP;
}

static int tegra_hv_pm_ctl_trigger_sys_shutdown(void)
{
	return -ENOTSUPP;
}

static int tegra_hv_pm_ctl_trigger_sys_reboot(void)
{
	return -ENOTSUPP;
}

static void print_message(struct aurix_tegra_spi_data *data)
{
#if SPI_DEBUG == 1
	struct spi_device *spi = data->spi;
	u8 *msg = (u8*) data->receive;
	int len = data->buf_len;
	int i;

	for (i = 0; i < len; i=i+4)
		dev_info(&spi->dev, "%02X %02X %02X %02X",
			msg[i], msg[i+1], msg[i+2], msg[i+3]);
#else

#endif
}

/*
 * return value: cmd_msg_id (SHUTDOWN, RESET or SUSPEND),
 * -1 otherwise (command not valid).
 */
static int read_cmd_message_id(struct aurix_tegra_spi_data *data)
{
	int ret;
	struct spi_device *spi = data->spi;
	struct packet *msg = data->receive;

	if (msg->src_layer_id != AURIX) {
		dev_err(&spi->dev, "Source is not Aurix\n");
		return -1;
	}
	if (msg->dest_layer_id != TEGRA) {
		dev_err(&spi->dev, "Destination is not Tegra\n");
		return -1;
	}
	if (msg->class_id != FUSA) {
		dev_err(&spi->dev, "Class id not valid\n");
		return -1;
	}
	if (msg->rsp_flag != AURIX_TEGRA) {
		dev_err(&spi->dev, "RSP flag not valid\n");
		return -1;
	}

	switch(msg->cmd_msg_id) {
		case SHUTDOWN:
			ret = SHUTDOWN;
			break;
		case RESET:
			ret = RESET;
			break;
		case SUSPEND:
			ret = SUSPEND;
			break;
		default:
			ret = -1;
	}

	return ret;
}

/*
 * return value: 0 on success (valid command received),
 * negative value otherwise (failure to receive or command is not valid)
 */
static int aurix_tegra_receive(struct aurix_tegra_spi_data *data)
{
	int ret;
	struct spi_device *spi = data->spi;
	struct spi_message msg;
	u8	*tx_buf = (u8*) data->transmit;
	u8	*rx_buf = (u8*) data->receive;
	u8 len = data->buf_len;
	struct spi_transfer	tr = {
		.rx_buf = rx_buf,
		.tx_buf = tx_buf,
		.len = len,
	};

	memset(rx_buf, 0, len);
	memset(tx_buf, 0xFF, len);

	spi_message_init(&msg);
	spi_message_add_tail(&tr, &msg);
	ret = spi_sync(spi, &msg);

	BUG_ON(ret > 0);

	if (ret < 0) {
		dev_dbg(&spi->dev, "%s: spi_sync() ret val is %d\n",
			__func__, ret);
		return ret;
	}
	print_message(data);

	ret = read_cmd_message_id(data);
	if (ret < 0) {
		dev_err(&spi->dev, "%s: Command received not valid\n",
			__func__);
		return -1;
	}

	data->command = ret;

	return 0;
}

static int trigger_command(struct aurix_tegra_spi_data *data)
{
	int err;
	struct device *dev = &data->spi->dev;
	dev_dbg(&data->spi->dev, "%s: Triggering Command %x\n",
		__func__, data->command);

	switch(data->command) {
		case SHUTDOWN:
			err = tegra_hv_pm_ctl_trigger_sys_shutdown();
			if (err < 0) {
				dev_err(dev, "%s: trigger_sys_shutdown failed\n",
					__func__);
				return err;
			}
			break;
		case RESET:
			err = tegra_hv_pm_ctl_trigger_sys_reboot();
			if (err < 0) {
				dev_err(dev, "%s: trigger_sys_reboot failed\n",
					__func__);
				return err;
			}
			break;
		case SUSPEND:
			err = tegra_hv_pm_ctl_trigger_sys_suspend();
			if (err < 0) {
				dev_err(dev, "%s: trigger_sys_suspend failed\n",
					__func__);
				return err;
			}
			break;
		default:
			BUG();
	}

	return 0;
}

static int aurix_tegra_read_thread(void *data)
{
	struct aurix_tegra_spi_data *aurix_data = (struct aurix_tegra_spi_data *) data;
	struct device *dev = &aurix_data->spi->dev;
	unsigned long flags;
	int err = 0;

	/*
	 * Error from aurix_tegra_receive() is raised in one of
	 * the following cases:
	 * i) spi_sync() failed
	 * ii) command received not valid.
	 * On aurix_tegra_stop_kthread(), SIGINT is sent to kthread
	 * ONLY if kthread is still running (exited = false). That way,
	 * kthread unblocks from spi_sync() and exits with error value.
	 * Thus, checking if kthread_should_stop is not necessary.
	 */
	err = aurix_tegra_receive(aurix_data);
	if (err < 0) {
		dev_dbg(dev, "%s: Error receiving\n", __func__);
		goto ret;
	}
	err = trigger_command(aurix_data);
	if (err < 0)
		goto ret;

ret:
	spin_lock_irqsave(&aurix_data->lock, flags);
	aurix_data->exited = true;
	spin_unlock_irqrestore(&aurix_data->lock, flags);
#if defined(NV_KTHREAD_COMPLETE_AND_EXIT_PRESENT) /* Linux v5.17 */
	kthread_complete_and_exit(NULL, err);
#else
	complete_and_exit(NULL, err);
#endif
}

static const struct of_device_id aurix_tegra_ids[] = {
	{ .compatible = "aurix-tegra-spi", },
	{}
};
MODULE_DEVICE_TABLE(of, aurix_tegra_ids);

static int aurix_tegra_spi_probe(struct spi_device *spi)
{
	u32 value;
	int err = 0;
	struct aurix_tegra_spi_data *data;
	struct device_node *np = spi->dev.of_node;

	data = devm_kzalloc(&spi->dev, sizeof(*data), GFP_KERNEL);
	if (IS_ERR_OR_NULL(data))
		return -ENOMEM;

	spin_lock_init(&data->lock);
	data->exited = false;

	err = of_property_read_u32(np, "spi-max-frequency", &value);
	if (err < 0) {
		dev_err(&spi->dev, "no property for frequency\n");
		goto error;
	}
	spi->max_speed_hz = value;

	/*
	 * Slave can receive and transmit data in Mode 1
	 */
	spi->mode = SPI_CPHA;

	err = spi_setup(spi);
	if (err < 0) {
		dev_err(&spi->dev, "spi_setup failed!\n");
		goto error;
	}

	data->spi = spi;
	data->buf_len = sizeof(struct packet);
	data->transmit = devm_kzalloc(&spi->dev, data->buf_len, GFP_KERNEL);
	if (IS_ERR_OR_NULL(data->transmit)) {
		err = -ENOMEM;
		goto error;
	}
	data->receive = devm_kzalloc(&spi->dev, data->buf_len, GFP_KERNEL);
	if (IS_ERR_OR_NULL(data->receive)) {
		err = -ENOMEM;
		goto error;
	}

	/*
	 * Kernel thread handles the communication between Aurix and Tegra.
	 * It remains idle, until a request from Aurix is sent. Upon receiving,
	 * it triggers the corresponding command.
	 */
	data->thread = kthread_run(aurix_tegra_read_thread,
		(void*) data, "aurix_tegra_kthread");
	if (IS_ERR_OR_NULL(data->thread)) {
		err = PTR_ERR(data->thread);
		goto error;
	}

	spi_set_drvdata(spi, data);
	return 0;

error:
	return err;
}

/*
 * Sending SIGINT to kthread unblocks it from spi_sync()
 * and forces it to exit. SIGINT should raised ONLY if
 * exited status is false, meaning that kthread is still
 * running. kthread_stop() is not needed since the kthread
 * does not make call to kthread_should_stop()
 */
static int aurix_tegra_stop_kthread(struct device *dev)
{
	struct aurix_tegra_spi_data *data = dev_get_drvdata(dev);
	int ret = 0;
	unsigned long flags;

	if (data->thread) {
		spin_lock_irqsave(&data->lock, flags);
		if (!data->exited)
			ret = send_sig(SIGINT, data->thread, 0);
		spin_unlock_irqrestore(&data->lock, flags);
		if (ret < 0) {
			dev_err(dev, "%s: Error sending SIGINT\n", __func__);
			return ret;
		}
	}
	return ret;
}

static int aurix_tegra_start_kthread(struct device *dev)
{
	struct aurix_tegra_spi_data *data = dev_get_drvdata(dev);
	int ret = 0;
	unsigned long flags;

	data->thread = kthread_run(aurix_tegra_read_thread,
		(void*) data, "aurix_tegra_kthread");
	if (IS_ERR_OR_NULL(data->thread)) {
		ret = PTR_ERR(data->thread);
		dev_err(dev, "%s: Error creating thread\n", __func__);
		return ret;
	}

	spin_lock_irqsave(&data->lock, flags);
	data->exited = false;
	spin_unlock_irqrestore(&data->lock, flags);
	return ret;
}

/*
 * remove, shutdown, suspend, resume functions
 */
#if defined(NV_SPI_DRIVER_STRUCT_REMOVE_RETURN_TYPE_INT) /* Linux 5.18 */
static int aurix_tegra_spi_remove(struct spi_device *spi)
{
	return aurix_tegra_stop_kthread(&spi->dev);
}
#else
static void aurix_tegra_spi_remove(struct spi_device *spi)
{
	aurix_tegra_stop_kthread(&spi->dev);
}
#endif

static void aurix_tegra_spi_shutdown(struct spi_device *spi)
{
	aurix_tegra_stop_kthread(&spi->dev);
}

static int aurix_tegra_spi_suspend(struct device *dev)
{
	return aurix_tegra_stop_kthread(dev);
}

static int aurix_tegra_spi_resume(struct device *dev)
{
	return aurix_tegra_start_kthread(dev);
}

static SIMPLE_DEV_PM_OPS(aurix_tegra_pm_ops,
			aurix_tegra_spi_suspend,
			aurix_tegra_spi_resume);

static struct spi_driver aurix_tegra_spi_driver = {
	.driver = {
		.owner	= THIS_MODULE,
		.name	= "aurix_tegra_spi",
		.of_match_table = of_match_ptr(aurix_tegra_ids),
		.pm = &aurix_tegra_pm_ops,
	},
	.probe		= aurix_tegra_spi_probe,
	.remove		= aurix_tegra_spi_remove,
	.shutdown	= aurix_tegra_spi_shutdown,
};

module_spi_driver(aurix_tegra_spi_driver);

MODULE_AUTHOR("Theodoros Marinakis <tmarinakis@nvidia.com>");
MODULE_DESCRIPTION("Aurix-Tegra SPI communication driver");
MODULE_LICENSE("GPL v2");
