// SPDX-License-Identifier: GPL-2.0-only
// SPDX-FileCopyrightText: Copyright (c) 2014-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
/*
 * tegra_hv_comm.c: TTY over Tegra HV
 *
 * Very loosely based on altera_jtaguart.c
 *
 * 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.
 */

#undef DEBUG

#include <nvidia/conftest.h>

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/platform_device.h>
#include <linux/io.h>

#include <nvidia/conftest.h>

#define DRV_NAME "tegra_hv_comm"

/* The port type is not important to userspace so use PORT_GENERIC. */
#ifndef PORT_GENERIC
#define PORT_GENERIC (-1)
#endif

#include <soc/tegra/virt/hv-ivc.h>

/* frame format is
* 0000: <size>
* 0004: <flags>
* 0008: data
*/

#define HDR_SIZE	8

struct tegra_hv_comm {
	struct uart_port port;
	struct platform_device *pdev;
	struct tegra_hv_ivc_cookie *ivck;
	unsigned int sigs;
	unsigned long imr;
	unsigned int tx_en:1;
	struct timer_list tx_timer;
	unsigned int rx_en:1;
	void *rx_frame;
	u8 *rx_buf;	/* always points to rx_frame + HDR_SIZE */
	void *tx_frame;
	u8 *tx_buf;	/* always points to tx_frame + HDR_SIZE */
	void *buf;
	int rx_in, rx_cnt;
};

#define to_tegra_hv_comm(_x) \
	container_of(_x, struct tegra_hv_comm, port)

static unsigned int tegra_hv_comm_tx_empty(struct uart_port *port)
{
	struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
	struct device *dev = &pp->pdev->dev;

	dev_dbg(dev, "%s\n", __func__);

	return tegra_hv_ivc_tx_empty(pp->ivck) ? TIOCSER_TEMT : 0;
}

static unsigned int tegra_hv_comm_get_mctrl(struct uart_port *port)
{
	/* fake it */
	return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
}

static void tegra_hv_comm_set_mctrl(struct uart_port *port, unsigned int sigs)
{
	struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
	struct device *dev = &pp->pdev->dev;

	/* nothing */
	dev_dbg(dev, "%s\n", __func__);
}

static int xmit_active(struct tegra_hv_comm *pp)
{
	struct uart_port *port = &pp->port;
#ifdef NV_UART_STATE_HAS_XMIT /* Linux v6.10 */
	struct circ_buf *xmit = &port->state->xmit;

	return port->x_char != 0 || uart_circ_chars_pending(xmit) > 0;
#else
	struct tty_port *tport = &port->state->port;

	return port->x_char != 0 || !kfifo_is_empty(&tport->xmit_fifo);
#endif
}

static void xmit_timer_setup(struct tegra_hv_comm *pp, int in_timer)
{
	if (xmit_active(pp)) {
		if (!pp->tx_en) {
			pp->tx_en = 1;
			mod_timer(&pp->tx_timer, jiffies + HZ / 8);
		}
	} else {
		if (pp->tx_en) {
			pp->tx_en = 0;
			if (!in_timer)
#if defined(NV_TIMER_DELETE_PRESENT) /* Linux v6.15 */
				timer_delete(&pp->tx_timer);
#else
				del_timer(&pp->tx_timer);
#endif
		}
	}
}

static void tegra_hv_comm_tx_chars(struct tegra_hv_comm *pp)
{
	struct uart_port *port = &pp->port;
	struct device *dev = &pp->pdev->dev;
#ifdef NV_UART_STATE_HAS_XMIT
	struct circ_buf *xmit = &port->state->xmit;
#else
	struct tty_port *tport = &port->state->port;
#endif
	unsigned int pending, count;
	char *s;
	int ret;

	dev_dbg(dev, "%s\n", __func__);

	if (!tegra_hv_ivc_can_write(pp->ivck))
		return;

	if (port->x_char) {
		/* Send special char - probably flow control */

		((u32 *)pp->tx_frame)[0] = 1;	/* count */
		((u32 *)pp->tx_frame)[1] = 0;	/* flags */
		s = pp->tx_frame + HDR_SIZE;
		*s++ = port->x_char;

		count = (void *)s - pp->tx_frame + HDR_SIZE;
		memset(pp->tx_frame + count, 0, pp->ivck->frame_size - count);
		ret = tegra_hv_ivc_write(pp->ivck, pp->tx_frame, count);
		if (ret != count) {
			/* error */
			dev_err(dev, "%s: failed to write x_char\n", __func__);
			return;
		}

		port->x_char = 0;
		port->icount.tx++;
		return;
	}

#ifdef NV_UART_STATE_HAS_XMIT
	pending = uart_circ_chars_pending(xmit);
#else
	pending = kfifo_len(&tport->xmit_fifo);
#endif
	if (pending > 0) {
		count = pp->ivck->frame_size - HDR_SIZE;
		if (count > pending)
			count = pending;
		if (count > 0) {
			pending -= count;

			((u32 *)pp->tx_frame)[0] = count;	/* count */
			((u32 *)pp->tx_frame)[1] = 0;		/* flags */
			s = pp->tx_frame + HDR_SIZE;
#ifdef NV_UART_STATE_HAS_XMIT
			while (count-- > 0) {
				*s++ = xmit->buf[xmit->tail];
				xmit->tail = (xmit->tail + 1) &
					(UART_XMIT_SIZE - 1);
				port->icount.tx++;
			}
#else
			count = kfifo_out(&tport->xmit_fifo, s, count);
			s += count;
			port->icount.tx += count;
#endif

			/* count is now from start of frame */
			count = (void *)s - pp->tx_frame;
			if (count > 0)
				memset(pp->tx_frame + count, 0,
						pp->ivck->frame_size - count);

			ret = tegra_hv_ivc_write(pp->ivck, pp->tx_frame, count);
			if (ret != count) {
				/* error */
				dev_err(dev, "%s: failed to write normal data\n",
						__func__);
				return;
			}

			if (pending < WAKEUP_CHARS)
				uart_write_wakeup(port);
		}
	}

}

static void tegra_hv_tx_timer_expired(struct timer_list *timer)
{
	struct tegra_hv_comm *pp = container_of(timer, struct tegra_hv_comm, tx_timer);
	struct uart_port *port = &pp->port;

	spin_lock(&port->lock);
	tegra_hv_comm_tx_chars(pp);
	xmit_timer_setup(pp, 1);
	spin_unlock(&port->lock);
}

static void tegra_hv_comm_start_tx(struct uart_port *port)
{
	struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
	struct device *dev = &pp->pdev->dev;

	dev_dbg(dev, "%s\n", __func__);

	tegra_hv_comm_tx_chars(pp);
	xmit_timer_setup(pp, 0);
}

static void tegra_hv_comm_stop_tx(struct uart_port *port)
{
	struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
	struct device *dev = &pp->pdev->dev;

	dev_dbg(dev, "%s\n", __func__);

	if (pp->tx_en) {
		pp->tx_en = 0;
#if defined(NV_TIMER_DELETE_PRESENT) /* Linux v6.15 */
		timer_delete_sync(&pp->tx_timer);
#else
		del_timer_sync(&pp->tx_timer);
#endif
	}
}

static void tegra_hv_comm_stop_rx(struct uart_port *port)
{
	struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
	struct device *dev = &pp->pdev->dev;

	dev_dbg(dev, "%s\n", __func__);

	pp->rx_en = 0;
}

static void tegra_hv_comm_break_ctl(struct uart_port *port, int break_state)
{
	/* nothing */
}

static void tegra_hv_comm_enable_ms(struct uart_port *port)
{
	/* nothing */
}

static void tegra_hv_comm_set_termios(struct uart_port *port,
					struct ktermios *termios,
					const struct ktermios *old)
{
	struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
	struct device *dev = &pp->pdev->dev;

	dev_dbg(dev, "%s\n", __func__);

	/* Just copy the old termios settings back */
	if (old)
		tty_termios_copy_hw(termios, old);
}

static void tegra_hv_comm_rx_chars(struct tegra_hv_comm *pp)
{
	struct uart_port *port = &pp->port;
	struct device *dev = &pp->pdev->dev;
	unsigned char ch, flag;
	u32 cnt, flags;
	int ret;

	dev_dbg(dev, "%s\n", __func__);

	for (;;) {
		/* no more data, get more */
		while (pp->rx_in >= pp->rx_cnt &&
				tegra_hv_ivc_can_read(pp->ivck)) {

			ret = tegra_hv_ivc_read(pp->ivck, pp->rx_frame,
					pp->ivck->frame_size);

			/* error */
			if (ret <= 0) {
				dev_err(dev, "%s: ivc_read failed (ret=%d)\n",
						__func__, ret);
				break;
			}

			/* not enough data */
			if (ret < HDR_SIZE) {
				dev_err(dev, "%s: ivc_read short read (ret=%d)\n",
						__func__, ret);
				break;
			}

			cnt = ((u32 *)pp->rx_frame)[0];
			flags = ((u32 *)pp->rx_frame)[1];

			/* validate */
			if (cnt > pp->ivck->frame_size - HDR_SIZE) {
				dev_err(dev, "%s: ivc_read bogus frame (cnt=%d)\n",
						__func__, cnt);
				break;
			}

			pp->rx_cnt = cnt;
			pp->rx_in = 0;
		}

		if (pp->rx_in >= pp->rx_cnt)
			break;

		/* next character */
		ch = pp->rx_buf[pp->rx_in++];

		flag = TTY_NORMAL;
		port->icount.rx++;

		if (uart_handle_sysrq_char(port, ch))
			continue;
		uart_insert_char(port, 0, 0, ch, flag);
	}

	tty_flip_buffer_push(&port->state->port);
}

static irqreturn_t tegra_hv_comm_interrupt(int irq, void *data)
{
	struct uart_port *port = data;
	struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
	struct device *dev = &pp->pdev->dev;

	dev_dbg(dev, "%s\n", __func__);

	spin_lock(&port->lock);

	/* until this function returns 0, the channel is unusable */
	if (tegra_hv_ivc_channel_notified(pp->ivck) == 0) {
		if (pp->rx_en && tegra_hv_ivc_can_read(pp->ivck))
			tegra_hv_comm_rx_chars(pp);

		tegra_hv_comm_tx_chars(pp);
		xmit_timer_setup(pp, 0);
	}

	spin_unlock(&port->lock);

	return IRQ_HANDLED;
}

static void tegra_hv_comm_config_port(struct uart_port *port, int flags)
{
	port->type = PORT_GENERIC;
}

static int tegra_hv_comm_startup(struct uart_port *port)
{
	struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
	struct device *dev = &pp->pdev->dev;
	unsigned long flags;
	int ret, irq;

	dev_dbg(dev, "%s\n", __func__);

	irq = pp->ivck->irq;
	ret = devm_request_irq(dev, irq,
			tegra_hv_comm_interrupt, 0,
			dev_name(dev), port);
	if (ret) {
		pr_err(DRV_NAME ": unable to request #%d irq=%d\n",
				port->line, irq);
		return ret;
	}

	spin_lock_irqsave(&port->lock, flags);
	pp->rx_en = 1;
	if (tegra_hv_ivc_can_read(pp->ivck))
		tegra_hv_comm_rx_chars(pp);
	spin_unlock_irqrestore(&port->lock, flags);

	return 0;
}

static void tegra_hv_comm_shutdown(struct uart_port *port)
{
	struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
	struct device *dev = &pp->pdev->dev;
	unsigned long flags;
	int irq;

	dev_dbg(dev, "%s\n", __func__);

	irq = pp->ivck->irq;

	devm_free_irq(&pp->pdev->dev, irq, port);

	spin_lock_irqsave(&port->lock, flags);
	pp->rx_en = 0;
	if (pp->tx_en) {
		pp->tx_en = 0;
#if defined(NV_TIMER_DELETE_PRESENT) /* Linux v6.15 */
		timer_delete(&pp->tx_timer);
#else
		del_timer(&pp->tx_timer);
#endif
	}
	spin_unlock_irqrestore(&port->lock, flags);
}

static const char *tegra_hv_comm_type(struct uart_port *port)
{
	return (port->type == PORT_GENERIC) ? "Tegra HV" : NULL;
}

static int tegra_hv_comm_request_port(struct uart_port *port)
{
	/* UARTs always present */
	return 0;
}

static void tegra_hv_comm_release_port(struct uart_port *port)
{
	/* Nothing to release... */
}

static int tegra_hv_comm_verify_port(struct uart_port *port,
				       struct serial_struct *ser)
{
	if (ser->type != PORT_UNKNOWN && ser->type != PORT_GENERIC)
		return -EINVAL;
	return 0;
}

/*
 *	Define the basic serial functions we support.
 */
static struct uart_ops tegra_hv_comm_ops = {
	.tx_empty	= tegra_hv_comm_tx_empty,
	.get_mctrl	= tegra_hv_comm_get_mctrl,
	.set_mctrl	= tegra_hv_comm_set_mctrl,
	.start_tx	= tegra_hv_comm_start_tx,
	.stop_tx	= tegra_hv_comm_stop_tx,
	.stop_rx	= tegra_hv_comm_stop_rx,
	.enable_ms	= tegra_hv_comm_enable_ms,
	.break_ctl	= tegra_hv_comm_break_ctl,
	.startup	= tegra_hv_comm_startup,
	.shutdown	= tegra_hv_comm_shutdown,
	.set_termios	= tegra_hv_comm_set_termios,
	.type		= tegra_hv_comm_type,
	.request_port	= tegra_hv_comm_request_port,
	.release_port	= tegra_hv_comm_release_port,
	.config_port	= tegra_hv_comm_config_port,
	.verify_port	= tegra_hv_comm_verify_port,
};

#define TEGRA_HV_COMM_MAJOR	204
#define TEGRA_HV_COMM_MINOR	213
#define TEGRA_HV_COMM_MAXPORTS 1
static struct tegra_hv_comm tegra_hv_comm_ports[TEGRA_HV_COMM_MAXPORTS];

static struct uart_driver tegra_hv_comm_driver = {
	.owner		= THIS_MODULE,
	.driver_name	= "tegra_hv_comm",
	.dev_name	= "ttyTGRHVC",
	.major		= TEGRA_HV_COMM_MAJOR,
	.minor		= TEGRA_HV_COMM_MINOR,
	.nr		= TEGRA_HV_COMM_MAXPORTS,
	.cons		= NULL,	/* no console supported yet */
};

static int tegra_hv_comm_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct device_node *dn, *hv_dn;
	struct tegra_hv_comm *pp;
	struct uart_port *port;
	int i = pdev->id;
	int ret;
	u32 id;

	if (!is_tegra_hypervisor_mode()) {
		dev_info(dev, "Hypervisor is not present\n");
		return -ENODEV;
	}

	/* If the platform_device does not have an ID (i.e., no numerical suffix in
	 * its device node name), treat it as having one port.
	 */
	if (i < 0)
		i = 0;

	if (i >= TEGRA_HV_COMM_MAXPORTS)
		return -EINVAL;

	dn = dev->of_node;
	if (dn == NULL) {
		dev_err(dev, "No OF data\n");
		return -EINVAL;
	}

	hv_dn = of_parse_phandle(dn, "ivc", 0);
	if (hv_dn == NULL) {
		dev_err(dev, "Failed to parse phandle of ivc prop\n");
		return -EINVAL;
	}

	ret = of_property_read_u32_index(dn, "ivc", 1, &id);
	if (ret != 0) {
		dev_err(dev, "Failed to read IVC property ID\n");
		of_node_put(hv_dn);
		return ret;
	}

	/* zero out */
	pp = &tegra_hv_comm_ports[i];
	memset(pp, 0, sizeof(*pp));

	port = &pp->port;

	pp->ivck = tegra_hv_ivc_reserve(hv_dn, id, NULL);

	of_node_put(hv_dn);

	if (IS_ERR_OR_NULL(pp->ivck)) {
		dev_err(dev, "Failed to reserve IVC channel %d\n", id);
		ret = PTR_ERR(pp->ivck);
		pp->ivck = NULL;
		return ret;
	}

	/* make sure the frame size is sufficient */
	if (pp->ivck->frame_size <= HDR_SIZE) {
		dev_err(dev, "frame size too small to support COMM\n");
		ret = -EINVAL;
		goto out_unreserve;
	}

	dev_info(dev, "Reserved IVC channel #%d - frame_size=%d\n",
			id, pp->ivck->frame_size);

	/* allocate temporary frames */
	pp->buf = devm_kzalloc(dev, pp->ivck->frame_size * 2, GFP_KERNEL);
	if (pp->buf == NULL) {
		dev_err(dev, "Failed to allocate buffers\n");
		ret = -ENOMEM;
		goto out_unreserve;
	}
	pp->rx_frame = pp->buf;
	pp->rx_buf = pp->rx_frame + HDR_SIZE;
	pp->tx_frame = pp->rx_frame + pp->ivck->frame_size;
	pp->tx_buf = pp->tx_frame + HDR_SIZE;

	timer_setup(&pp->tx_timer, tegra_hv_tx_timer_expired, 0);

	pp->pdev = pdev;
	platform_set_drvdata(pdev, pp);

	port->line = i;
	port->type = PORT_GENERIC;
	port->iotype = SERIAL_IO_MEM;
	port->ops = &tegra_hv_comm_ops;
	port->flags = UPF_BOOT_AUTOCONF;

	/*
	 * start the channel reset process asynchronously. until the reset
	 * process completes, any attempt to use the ivc channel will return
	 * an error (e.g., all transmits will fail.)
	 */
	tegra_hv_ivc_channel_reset(pp->ivck);

	ret = uart_add_one_port(&tegra_hv_comm_driver, port);
	if (ret != 0) {
		dev_err(dev, "uart_add_one_port failed\n");
		goto out_unreserve;
	}

	dev_info(dev, "ready\n");

	return 0;

out_unreserve:
	tegra_hv_ivc_unreserve(pp->ivck);
	return ret;
}

#if defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID) /* Linux v6.11 */
static void tegra_hv_comm_remove(struct platform_device *pdev)
#else
static int tegra_hv_comm_remove(struct platform_device *pdev)
#endif
{
	struct tegra_hv_comm *pp = platform_get_drvdata(pdev);
	struct uart_port *port;

	port = &pp->port;

	tegra_hv_ivc_unreserve(pp->ivck);

	uart_remove_one_port(&tegra_hv_comm_driver, port);

#if !defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID)
	return 0;
#endif
}

#ifdef CONFIG_OF
static struct of_device_id tegra_hv_comm_match[] = {
	{ .compatible = "nvidia,tegra-hv-comm", },
	{},
};
MODULE_DEVICE_TABLE(of, tegra_hv_comm_match);
#endif /* CONFIG_OF */

static struct platform_driver tegra_hv_comm_platform_driver = {
	.probe	= tegra_hv_comm_probe,
	.remove	= tegra_hv_comm_remove,
	.driver	= {
		.name		= DRV_NAME,
		.owner		= THIS_MODULE,
		.of_match_table	= of_match_ptr(tegra_hv_comm_match),
	},
};

static int tegra_hv_comm_init(void)
{
	int rc;

	rc = uart_register_driver(&tegra_hv_comm_driver);
	if (rc)
		return rc;
	rc = platform_driver_register(&tegra_hv_comm_platform_driver);
	if (rc)
		uart_unregister_driver(&tegra_hv_comm_driver);
	return rc;
}

static void tegra_hv_comm_exit(void)
{
	platform_driver_unregister(&tegra_hv_comm_platform_driver);
	uart_unregister_driver(&tegra_hv_comm_driver);
}

module_init(tegra_hv_comm_init);
module_exit(tegra_hv_comm_exit);

MODULE_AUTHOR("Pantelis Antoniou <pantoniou@nvidia.com>");
MODULE_DESCRIPTION("TTY over Tegra Hypervisor IVC channel");
MODULE_LICENSE("GPL");
