/*
 * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <io.h>
#include <kernel/dt.h>
#include <kernel/dt_driver.h>
#include <drivers/tegra_utc.h>

#define TEGRA_UTC_TX_CLIENT_ENABLE(base)	((base) + 0x0000)
#define TEGRA_UTC_TX_CLIENT_COMMAND(base)	((base) + 0x000c)
#define TEGRA_UTC_TX_CLIENT_DATA(base)		((base) + 0x0020)
#define TEGRA_UTC_TX_CLIENT_FIFO_STATUS(base)	((base) + 0x0100)

#define COMMAND_FLUSH				(0x10)

#define TX_STATUS_FULL				(0x2)
#define TIMEOUT_COUNT				(1000)

static vaddr_t chip_to_base(struct serial_chip *chip)
{
	struct tegra_utc_data *utcd =
		container_of(chip, struct tegra_utc_data, chip);
	return io_pa_or_va(&utcd->base, utcd->base_size);
}

static int32_t tegra_utc_wait_for_ready(struct serial_chip *chip)
{
	vaddr_t base = chip_to_base(chip);
	uint32_t cnt = 0;
	int32_t err = -1;

	do {
		uint32_t val = io_read32(TEGRA_UTC_TX_CLIENT_FIFO_STATUS(base));
		if ((val & TX_STATUS_FULL) == 0U) {
			err = 0;
			break;
		}
		cnt++;
	} while (cnt <= TIMEOUT_COUNT);

	return err;
}

static void tegra_utc_putc(struct serial_chip *chip, int c)
{
	vaddr_t base = chip_to_base(chip);
	int32_t err;

	err = tegra_utc_wait_for_ready(chip);
	if (err == 0) {
		io_write32(TEGRA_UTC_TX_CLIENT_DATA(base), c);
	}
}

static void tegra_utc_flush(struct serial_chip *chip)
{
	vaddr_t base = chip_to_base(chip);

	io_write32(TEGRA_UTC_TX_CLIENT_COMMAND(base), COMMAND_FLUSH);
}

static const struct serial_ops tegra_utc_ops = {
	.flush = tegra_utc_flush,
	.putc = tegra_utc_putc
};
DECLARE_KEEP_PAGER(tegra_utc_ops);

void tegra_utc_init(struct tegra_utc_data *utcd,
			struct io_pa_va base, size_t size)
{
	if (!utcd)
		return;

	utcd->base = base;
	utcd->base_size= size;
	utcd->chip.ops = &tegra_utc_ops;
}

#ifdef CFG_DT

static struct serial_chip *tegra_utc_dev_alloc(void)
{
	struct tegra_utc_data *utcd = nex_calloc(1, sizeof(*utcd));

	if (!utcd)
		return NULL;

	return &utcd->chip;
}

static int tegra_utc_dev_init(struct serial_chip *chip,
				const void *fdt, int offs,
				const char *params)
{
	struct tegra_utc_data *utcd = container_of(chip,
		struct tegra_utc_data, chip);
	/* This fake PA won't be used once dt_map_dev() succeeds */
	struct io_pa_va base = { .pa = 0xdeadbeef };
	size_t size;

	if (params && params[0])
		IMSG("tegra_utc: device parameters ignored (%s)", params);

	if (dt_map_dev(fdt, offs, &base.va, &size, DT_MAP_AUTO) < 0) {
		EMSG("tegra_utc: failed to map memory");
		return -1;
	}

	if (size < 0x1000) {
		EMSG("tegra_utc: unexpected register size: %zx", size);
		return -1;
	}

	tegra_utc_init(utcd, base, size);

	return 0;
}

static void tegra_utc_dev_free(struct serial_chip *chip)
{
	struct tegra_utc_data *utcd = container_of(chip,
		struct tegra_utc_data, chip);

	nex_free(utcd);
}

static const struct serial_driver tegra_utc_driver = {
	.dev_alloc = tegra_utc_dev_alloc,
	.dev_init = tegra_utc_dev_init,
	.dev_free = tegra_utc_dev_free,
};

static const struct dt_device_match tegra_utc_match_table[] = {
	{ .compatible = "nvidia,tegra264-utc" },
	{ 0 }
};

DEFINE_DT_DRIVER(tegra_utc_dt_driver) = {
	.name = "tegra-utc",
	.type = DT_DRIVER_UART,
	.match_table = tegra_utc_match_table,
	.driver = &tegra_utc_driver,
};

#endif /* CFG_DT */
