/* SPDX-License-Identifier: MIT */

#include <stdarg.h>

#include "adt.h"
#include "iodev.h"
#include "types.h"
#include "uart.h"
#include "uart_regs.h"
#include "utils.h"
#include "vsprintf.h"

#define UART_CLOCK 24000000

static u64 uart_base = 0;

int uart_init(void)
{
    int path[8];
    const char *uart_path;

    if (adt_path_offset_trace(adt, "/arm-io/uart6/debug-console", NULL) > 0) {
        uart_path = "/arm-io/uart6";
        /* T2 ADT does not have /arm-io/uart0/debug-console, but it is the correct UART */
    } else if (adt_path_offset_trace(adt, "/arm-io/uart0", NULL) > 0) {
        uart_path = "/arm-io/uart0";
    } else {
        printf("!!!Debug UART node not found!\n");
        return -1;
    }

    adt_path_offset_trace(adt, uart_path, path);

    if (adt_get_reg(adt, path, "reg", 0, &uart_base, NULL)) {
        printf("!!! Failed to get UART reg property!\n");
        return -1;
    }

    return 0;
}

void uart_putbyte(u8 c)
{
    if (!uart_base)
        return;

    while (!(read32(uart_base + UTRSTAT) & UTRSTAT_TXBE))
        ;

    write32(uart_base + UTXH, c);
}

u8 uart_getbyte(void)
{
    if (!uart_base)
        return 0;

    while (!(read32(uart_base + UTRSTAT) & UTRSTAT_RXD))
        ;

    return read32(uart_base + URXH);
}

void uart_putchar(u8 c)
{
    if (c == '\n')
        uart_putbyte('\r');

    uart_putbyte(c);
}

u8 uart_getchar(void)
{
    return uart_getbyte();
}

void uart_puts(const char *s)
{
    while (*s)
        uart_putchar(*(s++));

    uart_putchar('\n');
}

void uart_write(const void *buf, size_t count)
{
    const u8 *p = buf;

    while (count--)
        uart_putbyte(*p++);
}

size_t uart_read(void *buf, size_t count)
{
    u8 *p = buf;
    size_t recvd = 0;

    while (count--) {
        *p++ = uart_getbyte();
        recvd++;
    }

    return recvd;
}

void uart_setbaud(int baudrate)
{
    if (!uart_base)
        return;

    uart_flush();
    write32(uart_base + UBRDIV, ((UART_CLOCK / baudrate + 7) / 16) - 1);
}

void uart_flush(void)
{
    if (!uart_base)
        return;

    while (!(read32(uart_base + UTRSTAT) & UTRSTAT_TXE))
        ;
}

void uart_clear_irqs(void)
{
    if (!uart_base)
        return;

    write32(uart_base + UTRSTAT, UTRSTAT_TXTHRESH | UTRSTAT_RXTHRESH | UTRSTAT_RXTO);
}

int uart_printf(const char *fmt, ...)
{
    va_list args;
    char buffer[512];
    int i;

    va_start(args, fmt);
    i = vsnprintf(buffer, sizeof(buffer), fmt, args);
    va_end(args);

    uart_write(buffer, min(i, (int)(sizeof(buffer) - 1)));

    return i;
}

static bool uart_iodev_can_write(void *opaque)
{
    UNUSED(opaque);
    return true;
}

static ssize_t uart_iodev_can_read(void *opaque)
{
    UNUSED(opaque);

    if (!uart_base)
        return 0;

    return (read32(uart_base + UTRSTAT) & UTRSTAT_RXD) ? 1 : 0;
}

static ssize_t uart_iodev_read(void *opaque, void *buf, size_t len)
{
    UNUSED(opaque);
    return uart_read(buf, len);
}

static ssize_t uart_iodev_write(void *opaque, const void *buf, size_t len)
{
    UNUSED(opaque);
    uart_write(buf, len);
    return len;
}

static struct iodev_ops iodev_uart_ops = {
    .can_read = uart_iodev_can_read,
    .can_write = uart_iodev_can_write,
    .read = uart_iodev_read,
    .write = uart_iodev_write,
};

struct iodev iodev_uart = {
    .ops = &iodev_uart_ops,
    .usage = USAGE_CONSOLE | USAGE_UARTPROXY,
    .lock = SPINLOCK_INIT,
};
