blob: 6942a48f3f4f47197f9b3a1f4c5eb616f9f382c3 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2021 MediaTek Inc.
* Author: Andrew-sh.Cheng <andrew-sh.cheng@mediatek.com>
*/
#include <linux/clk.h>
#include <linux/devfreq.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/time.h>
#define MAX_VOLT_LIMIT (1150000)
struct cci_devfreq {
struct devfreq *devfreq;
struct regulator *cpu_reg;
struct clk *cci_clk;
int old_vproc;
unsigned long old_freq;
struct notifier_block opp_nb;
};
static int mtk_cci_set_voltage(struct cci_devfreq *cci_df, int vproc)
{
int ret;
ret = regulator_set_voltage(cci_df->cpu_reg, vproc,
MAX_VOLT_LIMIT);
if (!ret)
cci_df->old_vproc = vproc;
return ret;
}
static int mtk_cci_devfreq_target(struct device *dev, unsigned long *freq,
u32 flags)
{
int ret;
struct cci_devfreq *cci_df = dev_get_drvdata(dev);
struct dev_pm_opp *opp;
unsigned long opp_rate, opp_voltage, old_voltage;
if (!cci_df)
return -EINVAL;
if (cci_df->old_freq == *freq)
return 0;
opp_rate = *freq;
opp = devfreq_recommended_opp(dev, &opp_rate, 1);
opp_voltage = dev_pm_opp_get_voltage(opp);
dev_pm_opp_put(opp);
old_voltage = cci_df->old_vproc;
if (old_voltage == 0)
old_voltage = regulator_get_voltage(cci_df->cpu_reg);
// scale up: set voltage first then freq
if (opp_voltage > old_voltage) {
ret = mtk_cci_set_voltage(cci_df, opp_voltage);
if (ret) {
pr_err("cci: failed to scale up voltage\n");
return ret;
}
}
ret = clk_set_rate(cci_df->cci_clk, *freq);
if (ret) {
pr_err("%s: failed cci to set rate: %d\n", __func__,
ret);
mtk_cci_set_voltage(cci_df, old_voltage);
return ret;
}
// scale down: set freq first then voltage
if (opp_voltage < old_voltage) {
ret = mtk_cci_set_voltage(cci_df, opp_voltage);
if (ret) {
pr_err("cci: failed to scale down voltage\n");
clk_set_rate(cci_df->cci_clk, cci_df->old_freq);
return ret;
}
}
cci_df->old_freq = *freq;
return 0;
}
static int ccidevfreq_opp_notifier(struct notifier_block *nb,
unsigned long event, void *data)
{
struct dev_pm_opp *opp = data;
struct cci_devfreq *cci_df = container_of(nb, struct cci_devfreq,
opp_nb);
unsigned long freq, volt;
if (event == OPP_EVENT_ADJUST_VOLTAGE) {
freq = dev_pm_opp_get_freq(opp);
/* current opp item is changed */
if (freq == cci_df->old_freq) {
volt = dev_pm_opp_get_voltage(opp);
mtk_cci_set_voltage(cci_df, volt);
}
}
return 0;
}
static struct devfreq_dev_profile cci_devfreq_profile = {
.target = mtk_cci_devfreq_target,
};
static int mtk_cci_devfreq_probe(struct platform_device *pdev)
{
struct device *cci_dev = &pdev->dev;
struct cci_devfreq *cci_df;
struct devfreq_passive_data *passive_data;
struct notifier_block *opp_nb;
int ret;
cci_df = devm_kzalloc(cci_dev, sizeof(*cci_df), GFP_KERNEL);
if (!cci_df)
return -ENOMEM;
opp_nb = &cci_df->opp_nb;
cci_df->cci_clk = devm_clk_get(cci_dev, "cci_clock");
ret = PTR_ERR_OR_ZERO(cci_df->cci_clk);
if (ret) {
if (ret != -EPROBE_DEFER)
dev_err(cci_dev, "failed to get clock for CCI: %d\n",
ret);
return ret;
}
cci_df->cpu_reg = devm_regulator_get_optional(cci_dev, "proc");
ret = PTR_ERR_OR_ZERO(cci_df->cpu_reg);
if (ret) {
if (ret != -EPROBE_DEFER)
dev_err(cci_dev, "failed to get regulator for CCI: %d\n",
ret);
return ret;
}
ret = regulator_enable(cci_df->cpu_reg);
if (ret) {
dev_err(cci_dev, "enable buck for cci fail\n");
return ret;
}
ret = dev_pm_opp_of_add_table(cci_dev);
if (ret) {
dev_err(cci_dev, "Fail to get OPP table for CCI: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, cci_df);
passive_data = devm_kzalloc(cci_dev, sizeof(*passive_data), GFP_KERNEL);
if (!passive_data) {
ret = -ENOMEM;
goto err_opp;
}
passive_data->parent_type = CPUFREQ_PARENT_DEV;
cci_df->devfreq = devm_devfreq_add_device(cci_dev,
&cci_devfreq_profile,
DEVFREQ_GOV_PASSIVE,
passive_data);
if (IS_ERR(cci_df->devfreq)) {
ret = PTR_ERR(cci_df->devfreq);
dev_err(cci_dev, "cannot create cci devfreq device:%d\n", ret);
goto err_opp;
}
opp_nb->notifier_call = ccidevfreq_opp_notifier;
dev_pm_opp_register_notifier(cci_dev, opp_nb);
return 0;
err_opp:
dev_pm_opp_of_remove_table(cci_dev);
return ret;
}
static int mtk_cci_devfreq_remove(struct platform_device *pdev)
{
struct device *cci_dev = &pdev->dev;
struct cci_devfreq *cci_df;
struct notifier_block *opp_nb;
cci_df = platform_get_drvdata(pdev);
opp_nb = &cci_df->opp_nb;
dev_pm_opp_unregister_notifier(cci_dev, opp_nb);
dev_pm_opp_of_remove_table(cci_dev);
regulator_disable(cci_df->cpu_reg);
return 0;
}
static const __maybe_unused struct of_device_id
mediatek_cci_of_match[] = {
{ .compatible = "mediatek,mt8183-cci" },
{ },
};
MODULE_DEVICE_TABLE(of, mediatek_cci_of_match);
static struct platform_driver cci_devfreq_driver = {
.probe = mtk_cci_devfreq_probe,
.remove = mtk_cci_devfreq_remove,
.driver = {
.name = "mediatek-cci-devfreq",
.of_match_table = of_match_ptr(mediatek_cci_of_match),
},
};
module_platform_driver(cci_devfreq_driver);
MODULE_DESCRIPTION("Mediatek CCI devfreq driver");
MODULE_AUTHOR("Andrew-sh.Cheng <andrew-sh.cheng@mediatek.com>");
MODULE_LICENSE("GPL v2");