/***************************************************************************
 * Copyright (C) 2026 Eclipse ThreadX contributors
 *
 * This program and the accompanying materials are made available under the
 * terms of the MIT License which is available at
 * https://opensource.org/licenses/MIT.
 *
 * AI Disclosure: This file was largely AI-generated by Copilot (Sonnet 4.6).
 * The AI-generated portions may be considered public domain (CC0-1.0)
 * and not subject to the project's licence. The human contributor has
 * reviewed and verified that the code is correct.
 *
 * SPDX-License-Identifier: MIT and CC0-1.0
 **************************************************************************/

#include <stddef.h>
#include <stdint.h>

#include "bits.h"
#include "io.h"
#include "pulp_mem_map.h"
#include "uart_driver.h"

#define UDMA_CTRL_CG_OFFSET   0x00U
#define UDMA_CTRL_UART0_CLKEN BIT(0)

#define UDMA_UART_RX_SADDR_OFFSET 0x00U
#define UDMA_UART_RX_SIZE_OFFSET  0x04U
#define UDMA_UART_RX_CFG_OFFSET   0x08U
#define UDMA_UART_TX_SADDR_OFFSET 0x10U
#define UDMA_UART_TX_SIZE_OFFSET  0x14U
#define UDMA_UART_TX_CFG_OFFSET   0x18U
#define UDMA_UART_STATUS_OFFSET   0x20U
#define UDMA_UART_SETUP_OFFSET    0x24U
#define UDMA_UART_VALID_OFFSET    0x30U
#define UDMA_UART_DATA_OFFSET     0x34U

#define UDMA_CFG_EN_BIT          BIT(4)
/* EN (bit 4) only; DATASIZE=0 for 8-bit transfers; 0x12 would set DATASIZE=1
 * (16-bit) and cause the EN bit to never clear for single-byte buffers. */
#define UDMA_UART_TX_CFG_ENABLE  0x10U
#define UDMA_UART_WORDLEN_8_BITS 3U
#define UDMA_UART_TX_ENABLE_BIT  BIT(8)
#define UDMA_UART_RX_ENABLE_BIT  BIT(9)
#define UDMA_UART_RX_POLLING_BIT BIT(4)
#define UDMA_UART_RX_CLEAN_BIT   BIT(5)
#define UDMA_UART_DIV_SHIFT      16U
#define UDMA_UART_VALID_DATA_BIT BIT(0)
#define UDMA_UART_STATUS_TX_BUSY BIT(0)
#define UDMA_UART_MAX_TRANSFER   0xFFFFU

static uint8_t uart_tx_byte[N_UART];

static uintptr_t uart_base(uint8_t uart_id)
{
    return (uintptr_t)(UDMA_CH_ADDR_UART + ((uintptr_t)uart_id * (uintptr_t)UDMA_CH_SIZE));
}

int uart_init(uint8_t uart_id, uint32_t baudrate, uint32_t periph_freq)
{
    uint32_t clk_div;
    uint32_t setup;
    uint32_t cg;

    if ((uart_id >= N_UART) || (baudrate == 0U) || (periph_freq == 0U))
    {
        return -1;
    }

    cg = readw((uintptr_t)(UDMA_CH_ADDR_CTRL + UDMA_CTRL_CG_OFFSET));
    cg |= (uint32_t)(UDMA_CTRL_UART0_CLKEN << uart_id);
    writew(cg, (uintptr_t)(UDMA_CH_ADDR_CTRL + UDMA_CTRL_CG_OFFSET));

    clk_div = periph_freq / baudrate;
    if (clk_div == 0U)
    {
        clk_div = 1U;
    }

    setup = (clk_div << UDMA_UART_DIV_SHIFT) |
            UDMA_UART_TX_ENABLE_BIT |
            UDMA_UART_RX_ENABLE_BIT |
            UDMA_UART_RX_POLLING_BIT |
            UDMA_UART_RX_CLEAN_BIT |
            (UDMA_UART_WORDLEN_8_BITS << 1U);
    writew(setup, uart_base(uart_id) + UDMA_UART_SETUP_OFFSET);

    writew(0U, uart_base(uart_id) + UDMA_UART_RX_SADDR_OFFSET);
    writew(0U, uart_base(uart_id) + UDMA_UART_RX_SIZE_OFFSET);
    writew(0U, uart_base(uart_id) + UDMA_UART_RX_CFG_OFFSET);

    return 0;
}

void uart_write_byte(uint8_t uart_id, uint8_t c)
{
    if (uart_id >= N_UART)
    {
        return;
    }

    uart_tx_byte[uart_id] = c;
    uart_write_buf(uart_id, &uart_tx_byte[uart_id], 1U);
}

int uart_read_byte(uint8_t uart_id)
{
    if (uart_id >= N_UART)
    {
        return -1;
    }

    if ((readw(uart_base(uart_id) + UDMA_UART_VALID_OFFSET) & UDMA_UART_VALID_DATA_BIT) == 0U)
    {
        return -1;
    }

    return (int)(readw(uart_base(uart_id) + UDMA_UART_DATA_OFFSET) & 0xFFU);
}

void uart_write_str(uint8_t uart_id, const char *str)
{
    if (str == NULL)
    {
        return;
    }

    while (*str != '\0')
    {
        uart_write_byte(uart_id, (uint8_t)*str);
        ++str;
    }
}

void uart_write_buf(uint8_t uart_id, const uint8_t *buf, uint32_t len)
{
    uintptr_t base_addr;
    const uint8_t *current;
    uint32_t remaining;

    if ((uart_id >= N_UART) || (buf == NULL) || (len == 0U))
    {
        return;
    }

    base_addr = uart_base(uart_id);
    current = buf;
    remaining = len;

    while (remaining > 0U)
    {
        uint32_t chunk = (remaining > UDMA_UART_MAX_TRANSFER) ? UDMA_UART_MAX_TRANSFER : remaining;

        while ((readw(base_addr + UDMA_UART_STATUS_OFFSET) & UDMA_UART_STATUS_TX_BUSY) != 0U)
        {
        }

        writew((uint32_t)(uintptr_t)current, base_addr + UDMA_UART_TX_SADDR_OFFSET);
        writew(chunk, base_addr + UDMA_UART_TX_SIZE_OFFSET);
        writew(UDMA_UART_TX_CFG_ENABLE, base_addr + UDMA_UART_TX_CFG_OFFSET);

        while ((readw(base_addr + UDMA_UART_TX_CFG_OFFSET) & UDMA_CFG_EN_BIT) != 0U)
        {
        }

        current += chunk;
        remaining -= chunk;
    }
}
