// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2020 MediaTek Inc.
 */
#include <linux/module.h>
#include <linux/device.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/devfreq.h>
#include <linux/pm_opp.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/arm-smccc.h>
#include <linux/soc/mediatek/mtk_dvfsrc.h>
#include <linux/soc/mediatek/mtk_sip_svc.h>

/* Query available frequencies. */
#define DVFSRC_DDR_DVFS_GET_FREQ_COUNT	0x7
#define DVFSRC_DDR_DVFS_GET_FREQ_INFO	0x5

#define MAX_FREQ_COUNT 12

struct dvfsrc_devfreq {
	struct devfreq_dev_profile profile;
	struct devfreq *devfreq;
	struct device *ctrl_dev;
	int freq_count;
	unsigned long freq_table[MAX_FREQ_COUNT];
	unsigned long rate;
};

static void dvfsrc_set_freq_level(struct dvfsrc_devfreq *dvfsrc,
				  u32 level)
{
	mtk_dvfsrc_send_request(dvfsrc->ctrl_dev,
		MTK_DVFSRC_CMD_DRAM_REQUEST,
		level);
}

static unsigned long dvfsrc_get_cur_freq(struct dvfsrc_devfreq *dvfsrc)
{
	u32 val;

	mtk_dvfsrc_query_info(dvfsrc->ctrl_dev,
		MTK_DVFSRC_CMD_DRAM_LEVEL_QUERY, &val);

	if (val > MAX_FREQ_COUNT - 1)
		return 0;

	return dvfsrc->freq_table[val];
}

static u32 dvfsrc_find_freq_level(struct dvfsrc_devfreq *dvfsrc,
				  unsigned long rate)
{
	u32 index;

	for (index = 0; index < dvfsrc->freq_count - 1; index++) {
		if (dvfsrc->freq_table[index] >= rate)
			break;
	}

	return index;
}

static int dvfsrc_devfreq_target(struct device *dev, unsigned long *freq,
				 u32 flags)
{
	struct dvfsrc_devfreq *dvfsrc = dev_get_drvdata(dev);
	struct dev_pm_opp *new_opp;
	unsigned long new_freq;
	u32 level;
	int ret = 0;

	new_opp = devfreq_recommended_opp(dev, freq, flags);
	if (IS_ERR(new_opp)) {
		ret = PTR_ERR(new_opp);
		dev_err(dev, "failed to get recommended opp: %d\n", ret);
		return ret;
	}
	new_freq = dev_pm_opp_get_freq(new_opp);
	dev_pm_opp_put(new_opp);

	if (dvfsrc->rate == new_freq)
		return 0;

	level = dvfsrc_find_freq_level(dvfsrc, new_freq);
	dvfsrc_set_freq_level(dvfsrc, level);

	dvfsrc->rate = dvfsrc_get_cur_freq(dvfsrc);
	*freq = new_freq;

	return ret;
}

static int dvfsrc_devfreq_get_cur_freq(struct device *dev, unsigned long *freq)
{
	struct dvfsrc_devfreq *dvfsrc = dev_get_drvdata(dev);

	*freq = dvfsrc->rate;

	return 0;
}

static int dvfsrc_devfreq_get_dev_status(struct device *dev, struct devfreq_dev_status *stat)
{
	struct dvfsrc_devfreq *dvfsrc = dev_get_drvdata(dev);

	stat->busy_time = 0;
	stat->total_time = 0;
	stat->current_frequency = dvfsrc->rate;

	return 0;
}

static int dvfsrc_init_freq_info(struct device *dev)
{
	struct dvfsrc_devfreq *dvfsrc = dev_get_drvdata(dev);
	struct arm_smccc_res res;
	int index, err;
	unsigned long rate;

	arm_smccc_smc(MTK_SIP_VCOREFS_CONTROL, DVFSRC_DDR_DVFS_GET_FREQ_COUNT,
			0, 0, 0, 0, 0, 0, &res);
	if (res.a0)
		return -ENODEV;

	dvfsrc->freq_count = res.a1;
	if (dvfsrc->freq_count <= 0 || dvfsrc->freq_count > MAX_FREQ_COUNT)
		return -EINVAL;

	for (index = 0; index < dvfsrc->freq_count; ++index) {
		arm_smccc_smc(MTK_SIP_VCOREFS_CONTROL,
				DVFSRC_DDR_DVFS_GET_FREQ_INFO,
				index, 0, 0, 0, 0, 0, &res);
		if ((res.a0) || (long)res.a1 <= 0)
			return -EINVAL;
		/* return freq as khz*/
		rate = res.a1 * 1000;

		err = dev_pm_opp_add(dev, rate, 0);
		if (err) {
			dev_err(dev, "failed to add opp: %d\n", err);
			return err;
		}

		dvfsrc->freq_table[index] = rate;
	}

	return 0;
}

static int dvfsrc_devfreq_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct dvfsrc_devfreq *dvfsrc;
	const char *gov = DEVFREQ_GOV_USERSPACE;
	int ret;

	dvfsrc = devm_kzalloc(dev, sizeof(*dvfsrc), GFP_KERNEL);
	if (!dvfsrc)
		return -ENOMEM;

	platform_set_drvdata(pdev, dvfsrc);
	ret = dvfsrc_init_freq_info(dev);
	if (ret) {
		dev_err(dev, "failed to init dram freq info: %d\n", ret);
		goto err_free_opp;
	}
	dvfsrc->ctrl_dev = dev->parent;
	dvfsrc->profile.target = dvfsrc_devfreq_target;
	dvfsrc->profile.get_cur_freq = dvfsrc_devfreq_get_cur_freq;
	dvfsrc->profile.get_dev_status = dvfsrc_devfreq_get_dev_status;
	dvfsrc->profile.initial_freq = 0;
	dvfsrc->devfreq = devm_devfreq_add_device(dev,
		&dvfsrc->profile, gov, NULL);

	if (IS_ERR(dvfsrc->devfreq)) {
		ret = PTR_ERR(dvfsrc->devfreq);
		dev_err(dev, "failed to add devfreq device: %d\n", ret);
		goto err_free_opp;
	}
	return 0;

err_free_opp:
	dev_pm_opp_remove_all_dynamic(dev);

	return ret;
}

static int dvfsrc_devfreq_remove(struct platform_device *pdev)
{
	struct dvfsrc_devfreq *dvfsrc = platform_get_drvdata(pdev);

	devfreq_remove_device(dvfsrc->devfreq);
	dev_pm_opp_remove_all_dynamic(&pdev->dev);

	return 0;
}

static struct platform_driver dvfsrc_devfreq_platdrv = {
	.probe	= dvfsrc_devfreq_probe,
	.remove = dvfsrc_devfreq_remove,
	.driver = {
		.name	= "mtk-dvfsrc-devfreq",
	},
};
module_platform_driver(dvfsrc_devfreq_platdrv);
MODULE_DESCRIPTION("MTK DVFSRC devfreq driver");
MODULE_AUTHOR("Arvin wang <arvin.wang@mediatek.com>");
MODULE_LICENSE("GPL v2");

