// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
/*
 * Copyright (c) 2023 MediaTek Inc.
 */

#include <common.h>
#include <command.h>
#include <time.h>
#include <fs.h>
#include <MsCommon.h>
#include <debug_impl.h>
#include <iniutility.h>
#include <voc_impl.h>
#include <dt-bindings/mailbox/mt5896_mbox_chan_id_maps.h>
#include "mtk-pm.h"
#include "utility.h"

#include <dm/uclass.h> //ofnode
#include <linux/ioport.h>
#include <linux/io.h>
#include <fdt_support.h>

DECLARE_GLOBAL_DATA_PTR;

#define VOC_CONFIG_BUFF_LEN		32
#define VOC_CONFIG_INT_BASE		10
#define VOC_MIC_GAIN_BASE		(-1)
#define VOC_MIC_HPF_COEF_BASE		(-2)
#define VOC_MIC_GAIN			"Mic Gain Step"
#define VOC_MIC_HPF_STAGE		"HPF Switch"
#define VOC_MIC_HPF_COEF		"HPF Coef"
#define VOC_MIC_SEPERATE_START		":"
#define VOC_MIC_SEPERATE_END            "}"

#define MBOX_CM4_CONTROLLER_NAME	"mailbox_to_cm4" // arch/arm/dts/mt5896_mailbox.dtsi
#define MBOX_CM4_RX_TIMEOUT		(5000000)
#define MBOX_CM4_U8_MASK		(0xFF)
#define MBOX_CM4_U8_SHIFT		(8)
#define MAX_CMD_SIZE			(62)
#define MAX_VOC_MBOX_DELAY		(100)
#define MAX_VOC_MBOX_RETRY		(100)

#define VAD_DEVNODE "/vad_rproc"
#define PMU_DEVNODE "/pmu_rproc"
#define VOC_DEVNODE "/voc_audio"

#define OWL_ADDR_NUM            (4)
#define OWL_ADDR_SHIFT          (32)
#define OWL_ADDR_IDX0           (0)
#define OWL_ADDR_IDX1           (1)
#define OWL_ADDR_REG_IDX        (0)
#define OWL_ADDR_REG_SHIFT      (16)
#define OWL_ADDR_REG_OFFSET     (((0x1F << 1) * 2) + 0)
#define MASK_0XFFF0             (0xFFF0)

#define WAKEUP_SOURCE "voice"
#define WAKEUP_BY_PM (2)

// Local Variables
//--------------------------------------------------------------------------------------------------


//--------------------------------------------------------------------------------------------------
// Functions
//--------------------------------------------------------------------------------------------------
int mtk_voc_parse_file(const char *buffer, const char *prefix)
{
    char temp_buff[VOC_CONFIG_BUFF_LEN] = {0};
    char *pos, *pos_end;
    unsigned int count = 0;
    int ret = 0;

    if (buffer == NULL || prefix == NULL)
        goto mtk_voc_parse_value_fail;

    pos = strstr(buffer, prefix);
    if (pos == NULL)
        goto mtk_voc_parse_value_fail;

    pos = strstr(pos, VOC_MIC_SEPERATE_START);
    if (pos != NULL)
    {
        pos_end = strstr(pos, VOC_MIC_SEPERATE_END);
        if (pos_end != NULL)
        {
            while ((pos != pos_end) && ((count+1) < VOC_CONFIG_BUFF_LEN))
            {
                if (*pos == ' ' || *pos == ':')
                {
                    pos++;
                    continue;
                }
                temp_buff[count] = *pos;
                count++;
                pos++;
            }
            temp_buff[count] = 0;
            UBOOT_DEBUG("prefix:%s value:%s\n", prefix, temp_buff);
            ret = (int)simple_strtol(temp_buff, NULL, VOC_CONFIG_INT_BASE);//atoi(temp_buff);
        }
    }

mtk_voc_parse_value_fail:
    return ret;
}

int mtk_voc_parse_mic_config(const char *partition, const char *file, struct voc_mic_info *info)

{
    loff_t size;
    unsigned char *voice_file_buf;
    UBOOT_DEBUG("mtk_voc_parse_mic_config \n");

    voice_file_buf = read_storage_file_to_memory(partition, file, &size);
    if (voice_file_buf == NULL)
    {
        free(voice_file_buf);
        UBOOT_ERROR("Error: Read voice file to DRAM failure\n");
        return -1;
    }

    info->mic_gain = mtk_voc_parse_file((char *)voice_file_buf, VOC_MIC_GAIN);
    info->mic_hpf_stage = mtk_voc_parse_file((char *)voice_file_buf, VOC_MIC_HPF_STAGE);
    info->mic_hpf_coef = mtk_voc_parse_file((char *)voice_file_buf, VOC_MIC_HPF_COEF);

    free(voice_file_buf);
    return 0;
}

int mtk_voc_get_value(void)
{
    char cmd[MAX_CMD_SIZE] = {0};
    int ret = 0;

    ret = snprintf(cmd, sizeof(cmd) - 1, "mbox recv %s %d %d", MBOX_CM4_CONTROLLER_NAME, MBOX_CHAN_ID_VOICE, MBOX_CM4_RX_TIMEOUT);
    if (ret < 0)
    {
        UBOOT_DEBUG("snprintf fail %d\n", ret);
    }
    else
    {
        UBOOT_DEBUG("run_command(%s, 0).\n", cmd);
        if (0 != run_command(cmd, 0))
        {
            UBOOT_ERROR("run_command(%s, 0) failure.\n", cmd);
            return -1;
        }
        UBOOT_DEBUG("mtk_voc_get_value\n");
    }
    return 0;
}

int mtk_voc_set_value(int mbox_cmd, int value)
{
    int i = 0;
    char cmd[MAX_CMD_SIZE] = {0};
    int ret = 0;
    UBOOT_DEBUG("mtk_voc_set_value \n");

    if (value < 0)
    {
        ret = snprintf(cmd, sizeof(cmd) - 1, "mbox send %s %u %x %x %x",
                  MBOX_CM4_CONTROLLER_NAME, MBOX_CHAN_ID_VOICE, mbox_cmd,
                  ((unsigned int)(value) & MBOX_CM4_U8_MASK),
                  ((unsigned int)(value) >> MBOX_CM4_U8_SHIFT) & MBOX_CM4_U8_MASK);
    }
    else
    {
        ret = snprintf(cmd, sizeof(cmd) - 1, "mbox send %s %u %x %x",
                  MBOX_CM4_CONTROLLER_NAME, MBOX_CHAN_ID_VOICE, mbox_cmd, value);
    }

    if (ret < 0)
    {
        UBOOT_DEBUG("snprintf fail %d\n", ret);
    }
    else
    {
        UBOOT_DEBUG("run_command(%s, 0).\n", cmd);
        while (0 != run_command(cmd, 0))
        {
            i++;
            mdelay(MAX_VOC_MBOX_DELAY);
            if (i > MAX_VOC_MBOX_RETRY)
            {
                UBOOT_DEBUG("run_command(%s, 0) failure.\n", cmd);
                ret = -1;
                break;
            }
        }
    }
    return ret;
}

int check_cm4_dts_node(void)
{
    ofnode node_cm4;

    node_cm4 = ofnode_path(VAD_DEVNODE);
    if (ofnode_valid(node_cm4) && ofnode_is_available(node_cm4))
    {
        return 1;
    }

    node_cm4 = ofnode_path(PMU_DEVNODE);
    if (ofnode_valid(node_cm4) && ofnode_is_available(node_cm4))
    {
        return 1;
    }

    return 0;
}

int check_vad_dts_node(void)
{
    ofnode node_cm4;

    node_cm4 = ofnode_path(VAD_DEVNODE);
    if (ofnode_valid(node_cm4) && ofnode_is_available(node_cm4))
        return 1;

    return 0;
}

int mtk_voc_get_owl_supporting_flag(struct voc_env_info voc_info)
{
    ofnode voc_node;
    ofnode mmap_node;
    struct resource regs;
    void __iomem *reg_owl;
    phys_addr_t owl_addr = 0;
    u32 addr[OWL_ADDR_NUM] = {0};
    u16 value;
    static u32 owl_support = 0;
    static bool owl_dts_initialized = 0;

    if (owl_dts_initialized)
    {
        return owl_support;
    }

    owl_dts_initialized = 1;

    voc_node = ofnode_path(VOC_DEVNODE);
    if (!ofnode_valid(voc_node) || !ofnode_is_available(voc_node))
    {
        UBOOT_DEBUG("ignore %s loading.\n", VOC_DEVNODE);
        return owl_support;
    }

    if (ofnode_read_u32(voc_node, "owl_support", (u32 *)&owl_support) == 0)
    {
        UBOOT_DEBUG("Read owl_support successed! Value=%d\n", owl_support);
    }
    else
    {
        UBOOT_DEBUG("No owl_support flag in dts.\n");
        owl_support = 0;
    }

    if (voc_info.enable_wakeup < WAKEUP_BY_PM)
    {
        owl_support = 0;
    }

    if (owl_support)
    {
        mmap_node = ofnode_path("/mmap_info/MI_VOC_OWL");
        if (ofnode_valid(mmap_node) &&
            (ofnode_read_u32_array(mmap_node, "reg", addr, OWL_ADDR_NUM) == 0))
        {
            owl_addr = (((u64)addr[OWL_ADDR_IDX0] << OWL_ADDR_SHIFT) |
                addr[OWL_ADDR_IDX1]);
        }

        if ((owl_addr != 0) &&
            (ofnode_read_resource(voc_node, OWL_ADDR_REG_IDX, &regs) == 0))
        {
            reg_owl = ioremap(regs.start, regs.end - regs.start);
            if (reg_owl != NULL)
            {
                value = readw(reg_owl + OWL_ADDR_REG_OFFSET);
                value |= (owl_addr >> OWL_ADDR_REG_SHIFT) & MASK_0XFFF0;
                writew(value, reg_owl + OWL_ADDR_REG_OFFSET);
                iounmap(reg_owl);
                UBOOT_DEBUG("Update OWL MMAP done\n");
            }
        }
    }

    return owl_support;
}

int mtk_voc_notify(struct voc_env_info voc_info, struct voc_mic_info voc_mic_info)
{
    UBOOT_DEBUG("mtk_voc_notify \n");

    if (mtk_voc_get_owl_supporting_flag(voc_info))
    {
        UBOOT_INFO("Setting OWL mode.\n");
        pm_set_wakeup_config("owl", true);
        return 0;
    }

    if (check_cm4_dts_node() == 0)
    {
        UBOOT_DEBUG("Skipped second_standby mode due to no cm4 dts node\n");
        return 0;
    }

    // mbox format : u8 data[0]=cmd , u8 data[1]=value , u8 data[2]=value , u8 data[3] ,  ...., u8 data[61]
    if (voc_info.enable_mic)
    {
        if (mtk_voc_set_value(VOICE_CMD_MIC_SWITCH, voc_info.enable_mic) < 0)
        {
            UBOOT_ERROR("mtk_voc_set_value(%d) failure.\n", VOICE_CMD_MIC_SWITCH);
        }
    }

    if (voc_info.enable_seamless)
    {
        if (mtk_voc_set_value(VOICE_CMD_SEAMLESS, voc_info.enable_seamless) < 0)
        {
            UBOOT_ERROR("mtk_voc_set_value(%d) failure.\n", VOICE_CMD_SEAMLESS);
        }
    }

    if (voc_info.enable_wakeup)
    {
        if (voc_mic_info.mic_gain)
        {
            if (mtk_voc_set_value(VOICE_CMD_MIC_GAIN,
                VOC_MIC_GAIN_BASE * voc_mic_info.mic_gain) < 0)
            {
                UBOOT_ERROR("mtk_voc_set_value(%d) failure.\n", VOICE_CMD_MIC_GAIN);
            }
        }

        if (voc_mic_info.mic_hpf_stage)
        {
            if (mtk_voc_set_value(VOICE_CMD_HPF_STAGE, voc_mic_info.mic_hpf_stage) < 0)
            {
                UBOOT_ERROR("mtk_voc_set_value(%d) failure.\n", VOICE_CMD_HPF_STAGE);
            }

            if (mtk_voc_set_value(VOICE_CMD_HPF_CONFIG,
                VOC_MIC_HPF_COEF_BASE + voc_mic_info.mic_hpf_coef) < 0)
            {
                UBOOT_ERROR("mtk_voc_set_value(%d) failure.\n", VOICE_CMD_HPF_CONFIG);
            }
        }

        if (mtk_voc_set_value(VOICE_CMD_UBOOT_WAKEUP_ENABLE, voc_info.enable_wakeup) < 0)
        {
            UBOOT_ERROR("mtk_voc_set_value(%d) failure.\n", VOICE_CMD_UBOOT_WAKEUP_ENABLE);
        }
    }

    return 0;
}

int mtk_voc_standby(struct voc_env_info voc_info)
{
    int i = 0;
    bool wakeup_enable = 0;
    char cmd[MAX_CMD_SIZE] = {0};
    int ret = 0;
    int owl_support;

    UBOOT_DEBUG("mtk_voc_standby \n");

    owl_support = mtk_voc_get_owl_supporting_flag(voc_info);
    if (check_cm4_dts_node() == 0 && owl_support == 0)
    {
        UBOOT_DEBUG("Skipped second_standby mode due to no cm4 dts node\n");
        UBOOT_ERROR("set voice wakeup source false \n");
        if (pm_set_wakeup_config(WAKEUP_SOURCE, false) < 0)
        {
            UBOOT_ERROR("disale voice wakeup fail\n");
        }
        return 0;
    }

    ret = pm_get_wakeup_config(WAKEUP_SOURCE, &wakeup_enable);
    if (ret < 0)
    {
        UBOOT_ERROR("Skipped voc standby due to doesn't know wakeup source\n");
        return 0;
    }

    if (wakeup_enable == 1 && owl_support == 0 && voc_info.enable_wakeup >= WAKEUP_BY_PM)
    {
        return 0;
    }

    if (check_vad_dts_node() == 1)
    {
        // mbox format : u8 data[0]=cmd , u8 data[1]=value , u8 data[2]=value , u8 data[3] ,  ...., u8 data[61]
        if (voc_info.enable_wakeup)
        {
            if (mtk_voc_set_value(VOICE_CMD_UBOOT_WAKEUP_ENABLE, 0) < 0)
            {
                UBOOT_ERROR("mtk_voc_set_value(%d) failure.\n", VOICE_CMD_MIC_SWITCH);
            }
        }

        if (voc_info.enable_mic)
        {
            if (mtk_voc_set_value(VOICE_CMD_MIC_SWITCH, 0) < 0)
            {
                UBOOT_ERROR("mtk_voc_set_value(%d) failure.\n", VOICE_CMD_MIC_SWITCH);
            }
        }

        ret = snprintf(cmd, sizeof(cmd) - 1, "stop_vad");
        if (ret < 0)
        {
            UBOOT_DEBUG("snprintf fail %d\n", ret);
        }
        else
        {
            UBOOT_DEBUG("run_command(%s, 0).\n", cmd);
            while (0 != run_command(cmd, 0))
            {
                i++;
                mdelay(MAX_VOC_MBOX_DELAY);
                if (i > MAX_VOC_MBOX_RETRY)
                {
                    UBOOT_ERROR("run_command(%s, 0) failure.\n", cmd);
                    break;
                }
            }
        }
    }

    return 0;
}
