/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include "hal/hal_uart.h"
#include "bsp/bsp.h"
#include "mcu/fe310_hal.h"
#include "env/freedom-e300-hifive1/platform.h"
#include <mcu/plic.h>

#include <assert.h>

/*
 * Only one UART on FE310.
 */
struct hal_uart {
    uint8_t u_open:1;
    uint8_t u_rx_stall:1;
    uint8_t u_tx_started:1;
    uint8_t u_rx_buf;
    hal_uart_rx_char u_rx_func;
    hal_uart_tx_char u_tx_func;
    hal_uart_tx_done u_tx_done;
    void *u_func_arg;
    uint32_t u_baudrate;
};
static struct hal_uart uart;

int
hal_uart_init_cbs(int port, hal_uart_tx_char tx_func, hal_uart_tx_done tx_done,
  hal_uart_rx_char rx_func, void *arg)
{
    struct hal_uart *u;

    if (port != 0) {
        return -1;
    }
    u = &uart;
    if (u->u_open) {
        return -1;
    }
    u->u_rx_func = rx_func;
    u->u_tx_func = tx_func;
    u->u_tx_done = tx_done;
    u->u_func_arg = arg;
    return 0;
}

static int
fe310_hal_uart_tx_fill_fifo(struct hal_uart *u)
{
    int data = 0;

    while ((int32_t)(UART0_REG(UART_REG_TXFIFO)) >= 0) {
        data = u->u_tx_func(u->u_func_arg);
        if (data < 0) {
            if (u->u_tx_done) {
                u->u_tx_done(u->u_func_arg);
            }
            /* No more interrupts for TX */
            UART0_REG(UART_REG_IE) &= ~UART_IP_TXWM;
            u->u_tx_started = 0;
            break;
        } else {
            UART0_REG(UART_REG_TXFIFO) = data;
        }
    }
    return data;
}

void
hal_uart_start_tx(int port)
{
    struct hal_uart *u;
    int sr;
    int rc;

    if (port != 0) {
        return;
    }
    u = &uart;
    __HAL_DISABLE_INTERRUPTS(sr);
    if (u->u_tx_started == 0) {
        UART0_REG(UART_REG_TXCTRL) |= UART_TXEN;
        rc = fe310_hal_uart_tx_fill_fifo(u);
        if (rc >= 0) {
            u->u_tx_started = 1;
            UART0_REG(UART_REG_IE) |= UART_IP_TXWM;
        }
    }
    __HAL_ENABLE_INTERRUPTS(sr);
}

void
hal_uart_start_rx(int port)
{
    struct hal_uart *u;
    int sr;
    int rc;

    if (port != 0) {
        return;
    }
    u = &uart;
    if (u->u_rx_stall) {
        __HAL_DISABLE_INTERRUPTS(sr);
        rc = u->u_rx_func(u->u_func_arg, u->u_rx_buf);
        if (rc == 0) {
            u->u_rx_stall = 0;
            UART0_REG(UART_REG_IE) |= UART_IP_RXWM;
        }

        __HAL_ENABLE_INTERRUPTS(sr);
    }
}

void
hal_uart_blocking_tx(int port, uint8_t data)
{
    struct hal_uart *u;

    if (port != 0) {
        return;
    }

    u = &uart;
    if (!u->u_open) {
        return;
    }

    UART0_REG(UART_REG_TXCTRL) |= UART_TXEN;
    while (UART0_REG(UART_REG_TXFIFO) < 0) {
    }
    UART0_REG(UART_REG_TXFIFO) = data;
}

static void
fe310_uart_irq_handler(int num)
{
    struct hal_uart *u;
    int rc;
    int data;

    u = &uart;
    /* RX Path */
    while (1) {
        data = UART0_REG(UART_REG_RXFIFO);
        if (data < 0) {
            break;
        }
        rc = u->u_rx_func(u->u_func_arg, (uint8_t)data);
        if (rc < 0) {
            /* Don't rise RX interrupts till next hal_uart_start_rx */
            UART0_REG(UART_REG_IE) &= ~UART_IP_RXWM;
            u->u_rx_buf = (uint8_t)data;
            u->u_rx_stall = 1;
        }
    }

    /* TX Path */
    if (u->u_tx_started) {
        fe310_hal_uart_tx_fill_fifo(u);
    }
}

int
hal_uart_init(int port, void *arg)
{
    uint32_t mask;
    (void)arg;

    if (port != 0) {
        return -1;
    }

    /* Configure RX/TX pins */
    mask = (1U << IOF_UART0_RX) | (1U << IOF_UART0_TX);
    GPIO_REG(GPIO_IOF_EN) |= mask;
    GPIO_REG(GPIO_IOF_SEL) &= ~mask;

    /* Install interrupt handler */
    plic_set_handler(INT_UART0_BASE, fe310_uart_irq_handler, 3);

    return 0;
}

int
hal_uart_config(int port, int32_t baudrate, uint8_t databits, uint8_t stopbits,
  enum hal_uart_parity parity, enum hal_uart_flow_ctl flow_ctl)
{
    struct hal_uart *u;

    if (port != 0) {
        return -1;
    }

    u = &uart;
    if (u->u_open) {
        return -1;
    }

    /*
     * pin config
     * UART config
     * nvic config
     * enable uart
     */
    if (databits != 8) {
        return -1;
    }
    if (stopbits > 2) {
        return -1;
    }

    switch (parity) {
    case HAL_UART_PARITY_NONE:
        break;
    case HAL_UART_PARITY_ODD:
    case HAL_UART_PARITY_EVEN:
        return -1;
    }

    switch (flow_ctl) {
    case HAL_UART_FLOW_CTL_NONE:
        break;
    case HAL_UART_FLOW_CTL_RTS_CTS:
        return -1;
    }
    UART0_REG(UART_REG_DIV) = (get_cpu_freq() + (baudrate / 2)) / baudrate - 1;
    /* Set threashold and stop bits, not enable yet */
    UART0_REG(UART_REG_TXCTRL) = UART_TXWM(4) | ((stopbits - 1) << 1);
    /* RX anebled with interrupt when any bytes is there */
    UART0_REG(UART_REG_RXCTRL) = UART_RXWM(0) | UART_RXEN;
    UART0_REG(UART_REG_IE) = UART_IP_RXWM;

    plic_enable_interrupt(INT_UART0_BASE);

    u->u_rx_stall = 0;
    u->u_tx_started = 0;
    u->u_open = 1;
    u->u_baudrate = baudrate;

    return 0;
}

int
hal_uart_close(int port)
{
    struct hal_uart *u;

    u = &uart;

    if (port == 0) {
        u->u_open = 0;
        UART0_REG(UART_REG_TXCTRL) &= ~UART_TXEN;
        UART0_REG(UART_REG_RXCTRL) = 0;
        plic_disable_interrupt(INT_UART0_BASE);
        return 0;
    }
    return -1;
}

void
hal_uart_sys_clock_changed(void)
{
    struct hal_uart *u;

    u = &uart;

    if (u->u_open) {
        UART0_REG(UART_REG_DIV) = (get_cpu_freq() + (u->u_baudrate / 2)) / u->u_baudrate - 1;
    }
}
