/*
 * platform_gx.c
 *
 * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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 the GNU General Public License for
 * more details.
 *
 */

#include <linux/platform_device.h>
#include <linux/version.h>
#include <linux/pm.h>
#include <linux/of.h>
#include <linux/module.h>            /* kernel module definitions */
#include <linux/ioport.h>            /* request_mem_region */
#include <linux/slab.h>
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 29))
#include <mach/register.h>
#include <mach/irqs.h>
#include <mach/io.h>
#endif
#include <asm/io.h>
#ifdef CONFIG_AMLOGIC_GPU_THERMAL
#include <linux/amlogic/gpu_cooling.h>
#include <linux/amlogic/gpucore_cooling.h>
//#include <linux/amlogic/aml_thermal_hw.h>
#include <linux/amlogic/meson_cooldev.h>
#endif

#include "mali_scaling.h"
#include "mali_clock.h"
#include "meson_main2.h"

/*
 *    For Meson 8 M2.
 *
 */
static void mali_plat_preheat(void);
static mali_plat_info_t mali_plat_data = {
    .bst_gpu = 210, /* threshold for boosting gpu. */
    .bst_pp = 160, /* threshold for boosting PP. */
    .have_switch = 1,
    .limit_on = 1,
    .plat_preheat = mali_plat_preheat,
};

static void mali_plat_preheat(void)
{
#ifndef CONFIG_MALI_DVFS
    u32 pre_fs;
    u32 clk, pp;

    if (get_mali_schel_mode() != MALI_PP_FS_SCALING)
        return;

    get_mali_rt_clkpp(&clk, &pp);
    pre_fs = mali_plat_data.def_clock + 1;
    if (clk < pre_fs)
        clk = pre_fs;
    if (pp < mali_plat_data.sc_mpp)
        pp = mali_plat_data.sc_mpp;
    set_mali_rt_clkpp(clk, pp, 1);
#endif
}

mali_plat_info_t* get_mali_plat_data(void) {
    return &mali_plat_data;
}

int get_mali_freq_level(int freq)
{
    int i = 0, level = -1;
    int mali_freq_num;

    if (freq < 0)
        return level;

    mali_freq_num = mali_plat_data.dvfs_table_size - 1;
    if (freq <= mali_plat_data.clk_sample[0])
        level = mali_freq_num-1;
    else if (freq >= mali_plat_data.clk_sample[mali_freq_num - 1])
        level = 0;
    else {
        for (i=0; i<mali_freq_num - 1 ;i++) {
            if (freq >= mali_plat_data.clk_sample[i] && freq <= mali_plat_data.clk_sample[i + 1]) {
                level = i;
                level = mali_freq_num-level - 1;
            }
        }
    }
    return level;
}

unsigned int get_mali_max_level(void)
{
    return mali_plat_data.dvfs_table_size - 1;
}

int get_gpu_max_clk_level(void)
{
    return mali_plat_data.cfg_clock;
}

#ifdef CONFIG_AMLOGIC_GPU_THERMAL
static void set_limit_mali_freq(u32 idx)
{
    if (mali_plat_data.limit_on == 0)
        return;
    if (idx > mali_plat_data.turbo_clock || idx < mali_plat_data.scale_info.minclk)
        return;
    if (idx > mali_plat_data.maxclk_sysfs) {
        printk("idx > max freq\n");
        return;
    }
    mali_plat_data.scale_info.maxclk= idx;
    revise_mali_rt();
}

static u32 get_limit_mali_freq(void)
{
    return mali_plat_data.scale_info.maxclk;
}

#ifdef CONFIG_DEVFREQ_THERMAL
static u32 get_mali_utilization(void)
{
#ifndef MESON_DRV_BRING
    return 55;
#else
    return (_mali_ukk_utilization_pp() * 100) / 256;
#endif
}
#endif
#endif

#ifdef CONFIG_AMLOGIC_GPU_THERMAL
static u32 set_limit_pp_num(u32 num)
{
    u32 ret = -1;
    if (mali_plat_data.limit_on == 0)
        goto quit;
    if (num > mali_plat_data.cfg_pp ||
            num < mali_plat_data.scale_info.minpp)
        goto quit;

    if (num > mali_plat_data.maxpp_sysfs) {
        printk("pp > sysfs set pp\n");
        goto quit;
    }

    mali_plat_data.scale_info.maxpp = num;
    revise_mali_rt();
    ret = 0;
quit:
    return ret;
}
#ifdef CONFIG_DEVFREQ_THERMAL
static u32 mali_get_online_pp(void)
{
    unsigned int val;
    mali_plat_info_t* pmali_plat = get_mali_plat_data();

    val = readl(pmali_plat->reg_base_aobus + 0xf0) & 0xff;
    if (val == 0x07)    /* No pp is working */
        return 0;

#ifndef MESON_DRV_BRING
    return 2;
#else
    return mali_executor_get_num_cores_enabled();
#endif
}
#endif
#endif

int mali_meson_init_start(struct platform_device* ptr_plt_dev)
{
    //dev_set_drvdata(&ptr_plt_dev->dev, &mali_plat_data);
    mali_dt_info(ptr_plt_dev, &mali_plat_data);
    mali_clock_init_clk_tree(ptr_plt_dev);
    return 0;
}

int mali_meson_init_finish(struct platform_device* ptr_plt_dev)
{
    if (mali_core_scaling_init(&mali_plat_data) < 0)
        return -1;
    return 0;
}

int mali_meson_uninit(struct platform_device* ptr_plt_dev)
{
    mali_core_scaling_term();
    return 0;
}

void mali_post_init(void)
{
#ifdef CONFIG_AMLOGIC_GPU_THERMAL
    int err;
    struct gpufreq_cooling_device *gcdev = NULL;
    struct gpucore_cooling_device *gccdev = NULL;

    gcdev = gpufreq_cooling_alloc();
    register_gpu_freq_info(get_current_frequency);
    if (IS_ERR(gcdev))
        printk("malloc gpu cooling buffer error!!\n");
    else if (!gcdev)
        printk("system does not enable thermal driver\n");
    else {
        gcdev->get_gpu_freq_level = get_mali_freq_level;
        gcdev->get_gpu_max_level = get_mali_max_level;
        gcdev->set_gpu_freq_idx = set_limit_mali_freq;
        gcdev->get_gpu_current_max_level = get_limit_mali_freq;
#ifdef CONFIG_DEVFREQ_THERMAL
        gcdev->get_gpu_freq = get_mali_freq;
        gcdev->get_gpu_loading = get_mali_utilization;
        gcdev->get_online_pp = mali_get_online_pp;
#endif
        err = gpufreq_cooling_register(gcdev);
#ifdef CONFIG_DEVFREQ_THERMAL
        meson_gcooldev_min_update(gcdev->cool_dev);
#endif
        if (err < 0)
            printk("register GPU  cooling error\n");
        printk("gpu cooling register okay with err=%d\n",err);
    }

    gccdev = gpucore_cooling_alloc();
    if (IS_ERR(gccdev))
        printk("malloc gpu core cooling buffer error!!\n");
    else if (!gccdev)
        printk("system does not enable thermal driver\n");
    else {
        gccdev->max_gpu_core_num=mali_plat_data.cfg_pp;
        gccdev->set_max_pp_num=set_limit_pp_num;
        err = (int)gpucore_cooling_register(gccdev);
#ifdef CONFIG_DEVFREQ_THERMAL
        meson_gcooldev_min_update(gccdev->cool_dev);
#endif
        if (err < 0)
            printk("register GPU  cooling error\n");
        printk("gpu core cooling register okay with err=%d\n",err);
    }
#endif
}
