/**
 * uart.c  —  UART driver implementation skeleton
 *
 * *** Student activity — fill in every TODO block ***
 *
 * Computer Architecture: Design and Analysis (2026 edition)
 * Krerk Piromsopa, Ph.D. · Chulalongkorn University
 */

#include "uart.h"
#include "platform.h"

/* ------------------------------------------------------------------ */
/*  Ring-buffer for interrupt-driven RX                                 */
/* ------------------------------------------------------------------ */
static volatile char   rx_buf[RX_BUF_SIZE];
static volatile uint32_t rx_head = 0;  /* write index (ISR writes here)  */
static volatile uint32_t rx_tail = 0;  /* read  index (main reads here)  */

/* ------------------------------------------------------------------ */
/*  uart_init  —  polling, 38400-8N1                                    */
/*                                                                      */
/*  The QEMU virt UART clock is 3686400 Hz.                             */
/*  Divisor for 38400 baud = 3686400 / (16 * 38400) = 6                */
/* ------------------------------------------------------------------ */
void uart_init(void)
{
    /* TODO Exercise 1, step 1:
     *   Set DLAB=1 in LCR so we can write the baud-rate divisor.
     *   Write divisor low byte (6) to offset UART_RBR (0).
     *   Write divisor high byte (0) to offset UART_IER (1).
     *   Clear DLAB (write UART_LCR_8N1 to LCR).
     *
     * Hint:
     *   UART_REG(UART0_BASE, UART_LCR) = UART_LCR_DLAB | UART_LCR_8N1;
     *   UART_REG(UART0_BASE, UART_RBR) = 6;   // divisor LSB
     *   UART_REG(UART0_BASE, UART_IER) = 0;   // divisor MSB
     *   UART_REG(UART0_BASE, UART_LCR) = UART_LCR_8N1;
     */

    /* TODO Exercise 1, step 2: Enable and reset FIFOs.
     *   UART_REG(UART0_BASE, UART_FCR) = UART_FCR_ENABLE |
     *                                    UART_FCR_RXRST  |
     *                                    UART_FCR_TXRST;
     */

    /* TODO Exercise 1, step 3: Keep interrupts disabled for polling.
     *   UART_REG(UART0_BASE, UART_IER) = 0;
     */
}

/* ------------------------------------------------------------------ */
/*  uart_init_irq  —  enable RX interrupt                               */
/* ------------------------------------------------------------------ */
void uart_init_irq(void)
{
    /* TODO Exercise 2, step 1: Enable RX-data-available interrupt.
     *   UART_REG(UART0_BASE, UART_IER) = UART_IER_ERBFI;
     */

    /* TODO Exercise 2, step 2: Configure PLIC.
     *   PLIC_PRIORITY(PLIC_SRC_UART0) = 1;        // non-zero priority
     *   PLIC_ENABLE0  |= (1u << PLIC_SRC_UART0);  // enable source
     *   PLIC_THRESHOLD = 0;                        // accept all priorities
     */

    /* TODO Exercise 2, step 3: Enable machine external interrupts.
     *   CSR_SET(mie, MIE_MEIE);
     *   CSR_SET(mstatus, MSTATUS_MIE);
     */
}

/* ------------------------------------------------------------------ */
/*  Polling transmit                                                     */
/* ------------------------------------------------------------------ */
void uart_putc(char c)
{
    /* TODO Exercise 1:
     *   Spin until TX Holding Register is Empty, then write character.
     *
     *   while (!(UART_REG(UART0_BASE, UART_LSR) & UART_LSR_THRE))
     *       ;
     *   UART_REG(UART0_BASE, UART_THR) = (uint8_t)c;
     */
    (void)c;  /* remove once implemented */
}

/* ------------------------------------------------------------------ */
/*  Polling receive                                                      */
/* ------------------------------------------------------------------ */
char uart_getc(void)
{
    /* TODO Exercise 1:
     *   Spin until Data Ready, then return received byte.
     *
     *   while (!(UART_REG(UART0_BASE, UART_LSR) & UART_LSR_DR))
     *       ;
     *   return (char)UART_REG(UART0_BASE, UART_RBR);
     */
    return 0;  /* remove once implemented */
}

/* ------------------------------------------------------------------ */
/*  String transmit                                                      */
/* ------------------------------------------------------------------ */
void uart_puts(const char *s)
{
    while (*s)
        uart_putc(*s++);
}

/* ------------------------------------------------------------------ */
/*  Interrupt-driven RX ISR                                             */
/* ------------------------------------------------------------------ */
void uart_rx_isr(void)
{
    /* TODO Exercise 2:
     *
     *  1. Claim the interrupt from PLIC:
     *       uint32_t src = PLIC_CLAIM_COMPLETE;  // read = claim
     *
     *  2. Read the received byte (clears DR flag):
     *       char c = (char)UART_REG(UART0_BASE, UART_RBR);
     *
     *  3. Enqueue into ring buffer (drop on overflow):
     *       uint32_t next = (rx_head + 1) & (RX_BUF_SIZE - 1);
     *       if (next != rx_tail) {
     *           rx_buf[rx_head] = c;
     *           rx_head = next;
     *       }
     *
     *  4. Complete the interrupt (write source ID back):
     *       PLIC_CLAIM_COMPLETE = src;
     */
}

/* ------------------------------------------------------------------ */
/*  Interrupt-driven dequeue                                            */
/* ------------------------------------------------------------------ */
char uart_getc_irq(void)
{
    if (rx_tail == rx_head)
        return 0;  /* buffer empty */

    /* TODO Exercise 2: Read from ring buffer.
     *   char c = rx_buf[rx_tail];
     *   rx_tail = (rx_tail + 1) & (RX_BUF_SIZE - 1);
     *   return c;
     */
    return 0;  /* remove once implemented */
}
