// SPDX-License-Identifier: GPL-2.0
/******************************************************************************
 *
 * Copyright(C) 2023 MediaTek Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See http://www.gnu.org/licenses/gpl-2.0.html for more details.
 *
 * 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.
 *
 *****************************************************************************/


#ifdef CONFIG_MALI_MIDGARD_DVFS

#include <linux/delay.h>
#include <mali_kbase.h>
#include "mali_kbase_platform_mtk_dtv.h"
#include "mali_kbase_platform_mtk_dtv_mt5896_reg.h"
/*
#include <linux/regulator/consumer.h>
*/

extern int mali_gpu_clock;
extern int optee_version;

#ifdef MTK_DTV_GPU_THERMAL
#define DEFAULT_T_SENSOR_REF            (772)
#define DEFAULT_T_SENSOR_SHIFT          (24000)
#define RESET_TEMP                      (113)
#define NORMAL_TEMP                     (85)
#define THERMAL_TRIM_VALUE              (770)
#endif

#define MTKTV_CPUFREQ_TA_UUID {0x1fc17090, 0xbbdf, 0x4648,{0xbc, 0xa9, 0x3a, 0xf9, 0x7a, 0xd8, 0x8e, 0x63}}

#define SEQUENCE_NUM (8)
#define E_GPU_DVFS_OPTEE_INIT     (4)
#define E_GPU_DVFS_OPTEE_ADJUST   (5)

#define TA_PARAM_NONE    (TEE_IOCTL_PARAM_ATTR_TYPE_NONE)
#define TA_PARAM_INPUT   (TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT)

typedef enum {
    TA_SUCCESS = 0x00000000,
    TA_ERROR   = 0xFFFF0006,
} ta_result;

static int _optee_match(struct tee_ioctl_version_data *data, const void *vers)
{
    return 1;
}

static void _uuid_to_octets(uint8_t d[TEE_IOCTL_UUID_LEN], const struct uuid *s)
{
    d[0] = s->low >> 24;
    d[1] = s->low >> 16;
    d[2] = s->low >> 8;
    d[3] = s->low;
    d[4] = s->middle >> 8;
    d[5] = s->middle;
    d[6] = s->high >> 8;
    d[7] = s->high;
    memcpy(d + 8, s->sequence, sizeof(s->sequence));
}

static u32 _InitializeContext(struct kbase_device* kbdev)
{
    if (optee_version == 3) {
        struct tee_ioctl_version_data vers = {
        .impl_id = TEE_OPTEE_CAP_TZ,
        .impl_caps = TEE_IMPL_ID_OPTEE,
        .gen_caps = TEE_GEN_CAP_GP,
    };

    kbdev->dvfs.ctx = tee_client_open_context(NULL, _optee_match, NULL, &vers);

    if (IS_ERR(kbdev->dvfs.ctx)) {
        pr_err("[%s][%d] context is NULL\n",__func__, __LINE__);
        return TA_ERROR;
    }

        return TA_SUCCESS;
    }
    pr_err("[%s][%d] only support optee version 3\n",__func__, __LINE__);
    return TA_ERROR;
}

static u32 _Open(struct kbase_device* kbdev)
{
    if (optee_version == 3) {
        struct tee_ioctl_open_session_arg arg;
        struct tee_param param = {0};
        int rc;

        memset(&arg, 0, sizeof(arg));
        _uuid_to_octets(arg.uuid, &kbdev->dvfs.cpufreq_uuid);

        arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC;
        arg.num_params = 1;

        rc = tee_client_open_session(kbdev->dvfs.ctx, &arg,(struct tee_param *) &param);
        if ((rc == TA_SUCCESS) && (arg.ret == TA_SUCCESS)) {
            kbdev->dvfs.session = arg.session;
            return TA_SUCCESS;
        }

        pr_err("[%s][%d] open session fail\n",__func__, __LINE__);
        return TA_ERROR;
    }
    pr_err("[%s][%d] only support optee version 3\n",__func__, __LINE__);
    return TA_ERROR;
}

static u32 _InvokeCmd(struct kbase_device* kbdev, u32 cmd_id, struct tee_param params)
{
    if (optee_version == 3) {
        struct tee_ioctl_invoke_arg arg;
        int rc;

        arg.num_params = 1;
        arg.session = kbdev->dvfs.session;
        arg.func = cmd_id;

        rc = tee_client_invoke_func(kbdev->dvfs.ctx, &arg, &params);
        if (rc) {
            pr_err("[%s][%d] invoke cmd failed\n",__func__, __LINE__);
        }

        return TA_SUCCESS;
    }
    pr_err("[%s][%d] only support optee version 3\n",__func__, __LINE__);
    return TA_ERROR;
}

/* Helper functions */
static void update_frequency(struct kbase_device* kbdev, u32 frequency)
{
    /* set GPU clock */
    ta_result res;
    struct tee_param params;

    if (frequency < MALI_MIN_FREQ)
    {
        return;
    }

    params.attr = TA_PARAM_INPUT;
    params.u.value.a = frequency;
    params.u.value.b = 0;

    res = _InvokeCmd(kbdev, E_GPU_DVFS_OPTEE_ADJUST, params);

    if (res != TA_SUCCESS){
        pr_err("[%s][%d] invoke fail, cmd id: %d\n",__func__, __LINE__, E_GPU_DVFS_OPTEE_ADJUST);
    }
}

/* DVFS callbacks */
static bool dvfs_enable(struct kbase_device* kbdev)
{
    KBASE_DEBUG_ASSERT(NULL != kbdev);

    kbdev->dvfs.current_frequency = kbdev->dvfs.dvfs_max_freq;
    kbdev->dvfs.enabled = 1;

    return true;
}

static bool dvfs_disable(struct kbase_device* kbdev)
{
    KBASE_DEBUG_ASSERT(NULL != kbdev);

    kbdev->dvfs.current_frequency = kbdev->dvfs.dvfs_max_freq; /* restore to the max frequency */
    kbdev->dvfs.enabled = 0;
    mali_gpu_clock = kbdev->dvfs.current_frequency;
    queue_work(kbdev->dvfs.workqueue_dvfs, &kbdev->dvfs.work_freq);

    return true;
}

static bool dvfs_clock_up(struct kbase_device* kbdev)
{
    u32 new_freq;

    KBASE_DEBUG_ASSERT(NULL != kbdev);
#ifdef MTK_DTV_GPU_THERMAL
    if (kbdev->dvfs.current_frequency >= kbdev->dvfs.dvfs_max_freq || kbdev->dvfs.current_frequency >= kbdev->dvfs.thermal_max_clock)
#else
    if (kbdev->dvfs.current_frequency >= kbdev->dvfs.dvfs_max_freq)
#endif
    {
        return true;
    }

    new_freq = kbdev->dvfs.dvfs_max_freq;
    kbdev->dvfs.current_frequency = new_freq;

    dev_dbg(kbdev->dev, "%s - GPU dvfs_clock_up \n", __func__);

    mali_gpu_clock = kbdev->dvfs.current_frequency;
    queue_work(kbdev->dvfs.workqueue_dvfs, &kbdev->dvfs.work_freq);

    return true;
}

static bool dvfs_clock_down(struct kbase_device* kbdev)
{
    u32 new_freq;

    KBASE_DEBUG_ASSERT(NULL != kbdev);

    if (kbdev->dvfs.current_frequency <= kbdev->dvfs.dvfs_min_freq)
    {
        return true;
    }

    new_freq = kbdev->dvfs.dvfs_min_freq;
    kbdev->dvfs.current_frequency = new_freq;

    dev_dbg(kbdev->dev, "%s - GPU dvfs_clock_down \n", __func__);

    mali_gpu_clock = kbdev->dvfs.current_frequency;
    queue_work(kbdev->dvfs.workqueue_dvfs, &kbdev->dvfs.work_freq);

    return true;
}

static bool dvfs_enable_boost(struct kbase_device* kbdev)
{
    if (!kbdev)
    {
        return false;
    }

    kbdev->dvfs.boost_enabled= 1;
    kbdev->dvfs.dvfs_max_freq = MALI_BOOST_FREQ;

    return true;
}

static bool dvfs_disable_boost(struct kbase_device* kbdev)
{
    if (!kbdev)
    {
        return false;
    }

    kbdev->dvfs.boost_enabled = 0;
    kbdev->dvfs.dvfs_max_freq = MALI_MAX_FREQ;

    return true;
}
#ifdef MTK_DTV_GPU_THERMAL
static bool dvfs_check_temp(struct kbase_device* kbdev)
{
    queue_work(kbdev->dvfs.workqueue_dvfs, &kbdev->dvfs.work_temp);
    dev_dbg(kbdev->dev, "%s - GPU current temperature %d\n", __func__, kbdev->dvfs.temperature);

    if(kbdev->dvfs.temperature > NORMAL_TEMP)
    {
        kbdev->dvfs.overheat = 1;
        kbdev->dvfs.thermal_max_clock = MALI_MIN_FREQ;
        dev_dbg(kbdev->dev, "%s - GPU overheat, set max clock to min %d\n", __func__, kbdev->dvfs.thermal_max_clock);
        dvfs_clock_down(kbdev);
    }
    else
    {
        if(kbdev->dvfs.overheat == 1)
        {
            kbdev->dvfs.thermal_max_clock = MALI_MAX_FREQ;
            kbdev->dvfs.overheat = 0;
        }
    }

    if(kbdev->dvfs.temperature > RESET_TEMP)
    {
        /* Trigger pm reset */
       dev_warn(kbdev->dev, "%s - GPU reach reset temperature, trigger reset!!!! \n", __func__);
       SET_REG_DIRECT(pm_misc, all_reset, 0x79);
    }

    return true;
}

void temp_handle(struct work_struct *work)
{
    struct kbase_device *kbdev;
    struct mtk_dtv_dvfs *dvfs;
    dvfs = container_of(work, struct mtk_dtv_dvfs, work_temp);
    kbdev = dvfs->kbdev;

    iio_read_channel_processed(kbdev->dvfs.SAR_Thermal, &kbdev->dvfs.temperature);
    kbdev->dvfs.temperature = ((DEFAULT_T_SENSOR_REF - kbdev->dvfs.temperature) * THERMAL_TRIM_VALUE)+ DEFAULT_T_SENSOR_SHIFT;
    kbdev->dvfs.temperature /= 1000;
}
#endif

void frequency_handle(struct work_struct *work)
{
    struct kbase_device *kbdev;
    struct mtk_dtv_dvfs *dvfs;
    dvfs = container_of(work, struct mtk_dtv_dvfs, work_freq);
    kbdev = dvfs->kbdev;
    update_frequency(kbdev, mali_gpu_clock);
}

/* Platform functions */
int dvfs_init(struct kbase_device* kbdev)
{
    ta_result res;
    struct tee_param params;
    struct uuid cpufreq_uuid = MTKTV_CPUFREQ_TA_UUID;

    KBASE_DEBUG_ASSERT(NULL != kbdev);

    memset(&kbdev->dvfs, 0, sizeof(kbdev->dvfs));

    kbdev->dvfs.dvfs_max_freq = MALI_MAX_FREQ;
    kbdev->dvfs.dvfs_min_freq = MALI_MIN_FREQ;

    kbdev->dvfs.enabled = 0;
    kbdev->dvfs.enable = dvfs_enable;
    kbdev->dvfs.disable = dvfs_disable;
    kbdev->dvfs.clock_up =dvfs_clock_up;
    kbdev->dvfs.clock_down = dvfs_clock_down;
    kbdev->dvfs.enable_boost = dvfs_enable_boost;
    kbdev->dvfs.disable_boost = dvfs_disable_boost;
    kbdev->dvfs.cpufreq_uuid = cpufreq_uuid;
    kbdev->dvfs.kbdev = kbdev;
#ifdef MTK_DTV_GPU_THERMAL
    kbdev->dvfs.thermal_max_clock = MALI_MAX_FREQ;
    kbdev->dvfs.check_temp = dvfs_check_temp;
    kbdev->dvfs.event_count = 1;
    kbdev->dvfs.temperature = 0;
    kbdev->dvfs.overheat = 0;
#endif

    /* Init TEE */
    res = _InitializeContext(kbdev);
    if (res != TA_SUCCESS) {
        pr_err("[%s][%d] initial context fail\n",__func__, __LINE__);
        return TA_ERROR;
    }
    res = _Open(kbdev);
    if (res != TA_SUCCESS) {
        pr_err("[%s][%d] open session fail\n",__func__, __LINE__);
        return TA_ERROR;
    }

    params.attr = TA_PARAM_NONE;
    params.u.value.a = 0;
    params.u.value.b = 0;

    res = _InvokeCmd(kbdev, E_GPU_DVFS_OPTEE_INIT, params);

    if (res != TA_SUCCESS) {
        pr_err("[%s][%d] invoke fail, cmd id: %d\n",__func__, __LINE__, E_GPU_DVFS_OPTEE_INIT);
        return TA_ERROR;
    }
    pr_info("[%s][%d] Gpufreq TA Init Successfully\n",__func__, __LINE__);

    /* enable after init */
    dvfs_enable(kbdev);

    /* Set regulator */
    /* reg_vgpu = kbdev->regulators[0];*/

    /* Init workqueue for work of adjusting volt/freq */
    kbdev->dvfs.workqueue_dvfs = alloc_workqueue("workqueue_dvfs", 0, 0);
    INIT_WORK(&kbdev->dvfs.work_freq, frequency_handle);

#ifdef MTK_DTV_GPU_THERMAL
    INIT_WORK(&kbdev->dvfs.work_temp, temp_handle);

    kbdev->dvfs.SAR_Thermal = devm_iio_channel_get(kbdev->dev, "adc13");
    if (IS_ERR(kbdev->dvfs.SAR_Thermal))
    {
        dev_err(kbdev->dev, "GPU get sar failed!\n");
        return PTR_ERR(kbdev->dvfs.SAR_Thermal);
    }
#endif
    return 0;
}

void dvfs_term(struct kbase_device* kbdev)
{

}

#endif /* CONFIG_MALI_MIDGARD_DVFS */
