/*
 * Copyright (c) 2013-2019, Huawei Technologies Co., Ltd. All rights reserved.
 * Copyright (c) 2020, Huawei Device Co., Ltd. All rights reserved.
 *
 * 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.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * 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 "amba_pl011.h"
#include "asm/platform.h"
#include "uart.h"
#include "los_hwi.h"
#include "los_spinlock.h"
#include "los_event.h"
#include "los_task_pri.h"

EVENT_CB_S g_stShellEvent;

CHAR g_inputCmd[CMD_LENGTH];
INT32 g_inputIdx = 0;
__attribute__ ((section(".data"))) UINT32 g_uart_fputc_en = 1;

#define REG32(addr) ((volatile UINT32 *)(UINTPTR)(addr))
#define UARTREG(base, reg)  (*REG32((base) + (reg)))
#define UART_FR_TXFF (0x1U << 5)

STATIC VOID UartPutcReg(UINTPTR base, CHAR c)
{
    /* Spin while fifo is full */
    while (UARTREG(base, UART_FR) & UART_FR_TXFF) {}
    UARTREG(base, UART_DR) = c;
}

STATIC INLINE UINTPTR uart_to_ptr(UINTPTR n)
{
    (VOID)n;
    return UART_REG_BASE;
}

INT32 uart_putc(INT32 port, CHAR c)
{
    UINTPTR base = uart_to_ptr((UINT32)port);
    UartPutcReg(base, c);
    return 1;
}

CHAR uart_fputc(CHAR c, VOID *f)
{
    (VOID)f;
    if (g_uart_fputc_en == 1) {
        if (c == '\n') {
            (VOID)uart_putc(0, '\r');
        }
        return (uart_putc(0, (c)));
    } else {
        return 0;
    }
}

LITE_OS_SEC_BSS STATIC SPIN_LOCK_INIT(g_uartOutputSpin);

STATIC VOID UartPutStr(UINTPTR base, const CHAR *s, UINT32 len)
{
    UINT32 i;

    for (i = 0; i < len; i++) {
        if (*(s + i) == '\n') {
            UartPutcReg(base, '\r');
        }
        UartPutcReg(base, *(s + i));
    }
}

UINT32 UartPutsReg(UINTPTR base, const CHAR *s, UINT32 len, BOOL isLock)
{
    UINT32 intSave;

    if (g_uart_fputc_en == 0) {
        return 0;
    }

    if (isLock) {
        LOS_SpinLockSave(&g_uartOutputSpin, &intSave);
        UartPutStr(base, s, len);
        LOS_SpinUnlockRestore(&g_uartOutputSpin, intSave);
    } else {
        UartPutStr(base, s, len);
    }

    return len;
}

VOID UartPuts(const CHAR *s, UINT32 len, BOOL isLock)
{
    UINTPTR base = uart_to_ptr(0);
    (VOID)UartPutsReg(base, s, len, isLock);
}

INT32 uart_puts(const CHAR *s, UINTPTR len, VOID *state)
{
    (VOID)state;
    UINTPTR i;

    for (i = 0; i < len; i++) {
        if (*(s + i) != '\0') {
            if (*(s + i) == '\n') {
                (VOID)uart_fputc('\r', NULL);
            }

            (VOID)uart_fputc(*(s + i), NULL);
        }
    }

    return (INT32)len;
}

VOID uart_handler(VOID)
{
    CHAR c;
    UINTPTR base = uart_to_ptr(0);

    c = UARTREG(base, UART_DR);

    switch (c) {
        case '\r':
        case '\n':
            if (g_inputIdx < CMD_LENGTH - 1) {
                g_inputCmd[g_inputIdx++] = '\0';
                LOS_EventWrite(&g_stShellEvent, 0x1);
                (VOID)uart_putc(0, '\r');
                (VOID)uart_putc(0, '\n');
            }
            break;
        case 0x8:   /* backspace */
        case 0x7f:  /* delete */
            if (g_inputIdx > 0) {
                g_inputIdx--;
                (VOID)uart_putc(0, '\b');
                (VOID)uart_putc(0, ' ');
                (VOID)uart_putc(0, '\b');
            }
            break;
        default:
            if (g_inputIdx < CMD_LENGTH - 1) {
                (VOID)uart_putc(0, c);
                g_inputCmd[g_inputIdx++] = c;
            }
    }
}

VOID uart_early_init(VOID)
{
    /* enable uart transmit */
    UARTREG(UART_REG_BASE, UART_CR) = (1 << 8) | (1 << 0);
}

VOID uart_init(VOID)
{
    UINT32 ret;

    /* uart interrupt priority should be the highest in interrupt preemption mode */
    ret = LOS_HwiCreate(NUM_HAL_INTERRUPT_UART, 0, 0, (HWI_PROC_FUNC)uart_handler, NULL);
    if (ret != LOS_OK) {
        PRINT_ERR("%s,%d, uart interrupt created error:%x\n", __FUNCTION__, __LINE__, ret);
    } else {
        /* clear all irqs */
        UARTREG(UART_REG_BASE, UART_ICR) = 0x3ff;

        /* set fifo trigger level */
        UARTREG(UART_REG_BASE, UART_IFLS) = 0;

        /* enable rx interrupt */
        UARTREG(UART_REG_BASE, UART_IMSC) = (1 << 4 | 1 << 6);

        /* enable receive */
        UARTREG(UART_REG_BASE, UART_CR) |= (1 << 9);

        HalIrqUnmask(NUM_HAL_INTERRUPT_UART);
    }
}
