// 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_mt5879_reg.h"

/* */
#define ADJUST_STEP         12 /* in MHz */

extern int mali_gpu_clock;

/* Helper functions */
static void update_frequency(u32 frequency)
{
   mali_gpu_clock = frequency;
    /* set GPU clock */
    SET_REG(GPUBRDGPLL, pd_gpubrdgpll, 0x0);
    SET_REG(GPUBRDGPLL, gc_gpubrdgpll_loopdiv_second, (frequency/GPU_CLOCK_STEP));
    udelay(1);
}

/* 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;
    update_frequency(kbdev->dvfs.current_frequency);

    return true;
}

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

    KBASE_DEBUG_ASSERT(NULL != kbdev);

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

    new_freq = kbdev->dvfs.current_frequency + ADJUST_STEP;

    if (new_freq > kbdev->dvfs.dvfs_max_freq)
    {
        new_freq = kbdev->dvfs.dvfs_max_freq;
    }
    else if (new_freq < kbdev->dvfs.dvfs_min_freq)
    {
        new_freq = kbdev->dvfs.dvfs_min_freq;
    }

    kbdev->dvfs.current_frequency = new_freq;
    update_frequency(kbdev->dvfs.current_frequency);

    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.current_frequency >= ADJUST_STEP) ? (kbdev->dvfs.current_frequency - ADJUST_STEP) : 0;

    if (new_freq < kbdev->dvfs.dvfs_min_freq)
    {
        new_freq = kbdev->dvfs.dvfs_min_freq;
    }
    else if (new_freq > kbdev->dvfs.dvfs_max_freq)
    {
        new_freq = kbdev->dvfs.dvfs_max_freq;
    }

    kbdev->dvfs.current_frequency = new_freq;
    update_frequency(kbdev->dvfs.current_frequency);

    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;
}

/* Platform functions */
int dvfs_init(struct kbase_device* kbdev)
{
    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;

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

    return 0;
}

void dvfs_term(struct kbase_device* kbdev)
{

}

#endif /* CONFIG_MALI_MIDGARD_DVFS */
