kernel-brax3-ubuntu-touch/drivers/misc/mediatek/apusys/power/2.5/common/apu-driver.c
erascape f319b992b1 kernel-5.15: Initial import brax3 UT kernel
* halium configs enabled

Signed-off-by: erascape <erascape@proton.me>
2025-09-23 15:17:10 +00:00

417 lines
9.5 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020 MediaTek Inc.
*/
#include <linux/err.h>
#include <linux/pm_runtime.h>
#include <linux/sched/clock.h>
#include <linux/slab.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include "governor.h"
#include "apusys_power_user.h"
#include "apusys_power.h"
#include "apu_common.h"
#include "apu_log.h"
#include "apu_of.h"
#include "apu_plat.h"
#include "apu_dbg.h"
int apu_device_power_suspend(enum DVFS_USER user, int is_suspend)
{
int ret = 0;
struct apu_dev *ad = NULL;
enum DVFS_USER ori_usr = user;
if (user == EDMA || user == EDMA2 || user == REVISER)
user = APUCB;
ad = apu_find_device(user);
if (IS_ERR_OR_NULL(ad)) {
apower_err(ad->dev, "[%s] called by no-exist user (%d)\n",
__func__, ori_usr);
ret = -EFAULT;
goto out;
}
apower_info(ad->dev, "[%s] called by %s\n",
__func__, apu_dev_string(ori_usr));
ret = pm_runtime_put_sync(ad->dev);
if (ret)
apower_err(ad->dev, "[%s] suspend fail, ret %d\n", __func__, ret);
out:
return ret;
}
EXPORT_SYMBOL(apu_device_power_suspend);
int apu_device_power_on(enum DVFS_USER user)
{
int ret = 0;
struct apu_dev *ad = NULL;
enum DVFS_USER ori_usr = user;
if (user == EDMA || user == EDMA2 || user == REVISER)
user = APUCB;
ad = apu_find_device(user);
if (IS_ERR_OR_NULL(ad)) {
apower_err(ad->dev, "[%s] called by no-exist user (%d)\n",
__func__, ori_usr);
ret = -EFAULT;
goto out;
}
apower_info(ad->dev, "[%s] called by %s\n", __func__, apu_dev_string(ori_usr));
ret = pm_runtime_get_sync(ad->dev);
/* Ignore suppliers with disabled runtime PM. */
if (ret < 0 && ret != -EACCES) {
apower_err(ad->dev, "[%s] fail, ret %d\n", __func__, ret);
pm_runtime_put_noidle(ad->dev);
goto out;
}
return 0;
out:
return ret;
}
EXPORT_SYMBOL(apu_device_power_on);
int apu_device_power_off(enum DVFS_USER user)
{
return apu_device_power_suspend(user, 0);
}
EXPORT_SYMBOL(apu_device_power_off);
void apu_device_set_opp(enum DVFS_USER user, uint8_t opp)
{
struct apu_dev *ad;
int freq, n_freq;
struct apu_gov_data *pgov_data;
ad = apu_find_device(user);
if (IS_ERR(ad)) {
pr_info("[%s] %s not support\n", __func__, apu_dev_string(user));
return;
}
if (!pm_runtime_active(ad->dev)) {
apower_warn(ad->dev, "[%s] already power off\n", __func__);
return;
}
if (apupw_dbg_get_fixopp())
return;
/* restrict opp in current spec */
if (opp < 0)
opp = 0;
if (opp > ad->df->profile->max_state - 1)
opp = ad->df->profile->max_state - 1;
/* calculate current freq and freq of boost user wants */
freq = ad->aclk->ops->get_rate(ad->aclk);
n_freq = apu_opp2freq(ad, opp);
if (round_Mhz(freq, n_freq))
return;
apower_info(ad->dev, "[%s] set opp %d, cur/next %dMhz/%dMhz",
__func__, opp, TOMHZ(freq), TOMHZ(n_freq));
/* update opp in governor data */
pgov_data = ad->df->data;
mutex_lock_nested(&ad->df->lock, pgov_data->depth);
pgov_data->req.value = opp;
list_sort(NULL, &pgov_data->head, apu_cmp);
mutex_unlock(&ad->df->lock);
}
EXPORT_SYMBOL(apu_device_set_opp);
uint64_t apu_get_power_info(int force)
{
if (!force)
queue_delayed_work(pm_wq, &pw_info_work, msecs_to_jiffies(1));
else
apupw_dbg_power_info(NULL);
return 0;
}
EXPORT_SYMBOL(apu_get_power_info);
uint8_t apusys_boost_value_to_opp(enum DVFS_USER user, uint8_t boost)
{
struct apu_dev *ad;
ad = apu_find_device(user);
if (IS_ERR(ad))
return PTR_ERR_OR_ZERO(ad);
return (uint8_t)apu_boost2opp(ad, boost);
}
EXPORT_SYMBOL(apusys_boost_value_to_opp);
ulong apusys_opp_to_freq(enum DVFS_USER user, uint8_t opp)
{
struct apu_dev *ad;
ad = apu_find_device(user);
if (IS_ERR(ad))
return PTR_ERR_OR_ZERO(ad);
return apu_opp2freq(ad, opp);
}
EXPORT_SYMBOL(apusys_opp_to_freq);
int8_t apusys_get_ceiling_opp(enum DVFS_USER user)
{
struct apu_dev *ad;
ulong rate = 0;
ad = apu_find_device(user);
if (IS_ERR(ad) || IS_ERR_OR_NULL(ad->aclk))
return PTR_ERR_OR_ZERO(ad);
rate = ad->aclk->ops->get_rate(ad->aclk);
return apu_freq2opp(ad, rate);
}
EXPORT_SYMBOL(apusys_get_ceiling_opp);
bool apu_get_power_on_status(enum DVFS_USER user)
{
struct apu_dev *ad;
if (user == EDMA || user == EDMA2 || user == REVISER)
user = APUCB;
ad = apu_find_device(user);
if (IS_ERR(ad))
return PTR_ERR_OR_ZERO(ad);
return !pm_runtime_status_suspended(ad->dev);
}
EXPORT_SYMBOL(apu_get_power_on_status);
int8_t apusys_get_opp(enum DVFS_USER user)
{
struct apu_dev *ad;
int freq = 0;
ad = apu_find_device(user);
if (IS_ERR(ad))
return PTR_ERR_OR_ZERO(ad);
freq = ad->aclk->ops->get_rate(ad->aclk);
return apu_freq2opp(ad, freq);
}
EXPORT_SYMBOL(apusys_get_opp);
void apu_power_reg_dump(void)
{
//TODO
}
EXPORT_SYMBOL(apu_power_reg_dump);
bool apusys_power_check(void)
{
/* TODO
* char *pwr_ptr;
* bool pwr_status = true;
*
* pwr_ptr = strstr(saved_command_line,
* "apusys_status=normal");
* if (pwr_ptr == 0) {
* pwr_status = false;
* pr_info("apusys power disable !!, pwr_status=%d\n",
* pwr_status);
* }
* pr_info("apusys power check, pwr_status=%d\n",
* pwr_status);
* return pwr_status;
*/
return true;
}
EXPORT_SYMBOL(apusys_power_check);
void apu_qos_set_vcore(int opp)
{
struct apu_dev *ad;
struct apu_gov_data *pgov_data;
int freq, n_freq;
/* get APUVCORE devfreq device */
ad = apu_find_device(APUCORE);
if (IS_ERR(ad))
return;
if (apupw_dbg_get_fixopp())
return;
/*
* comparing current freq with voted freq,
* if the same, just return.
*/
freq = ad->aclk->ops->get_rate(ad->aclk);
n_freq = apu_opp2freq(ad, opp);
if (round_Mhz(freq, n_freq))
return;
apower_info(ad->dev, "[%s] set opp %d, cur/next %dMhz/%dMhz",
__func__, opp, TOMHZ(freq), TOMHZ(n_freq));
/* get governor data and synchronize calling update_devfreq */
pgov_data = ad->df->data;
mutex_lock(&ad->df->lock);
pgov_data->req.value = opp;
list_sort(NULL, &pgov_data->head, apu_cmp);
update_devfreq(ad->df);
mutex_unlock(&ad->df->lock);
}
EXPORT_SYMBOL(apu_qos_set_vcore);
int apu_power_device_register(enum DVFS_USER user, struct platform_device *pdev)
{
return 0;
}
EXPORT_SYMBOL(apu_power_device_register);
void apu_power_device_unregister(enum DVFS_USER user)
{
pr_debug("[%s] user %d\n", user);
}
EXPORT_SYMBOL(apu_power_device_unregister);
int apu_power_callback_device_register(enum POWER_CALLBACK_USER user,
void (*power_on_callback)(void *para),
void (*power_off_callback)(void *para))
{
return apu_power_cb_register(user, power_on_callback, power_off_callback);
}
EXPORT_SYMBOL(apu_power_callback_device_register);
void apu_power_callback_device_unregister(enum POWER_CALLBACK_USER user)
{
return apu_power_cb_unregister(user);
}
EXPORT_SYMBOL(apu_power_callback_device_unregister);
static int apusys_power_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
int err = 0;
dev_info(&pdev->dev, "%s\n", __func__);
/* initial run time power management */
pm_runtime_enable(dev);
/* Enumerate child at last, since child need parent's devfreq */
err = of_platform_populate(dev->of_node, NULL, NULL, dev);
if (err) {
dev_info(dev, "%s populate fail\n", __func__);
return err;
}
return err;
}
static int apusys_power_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "%s %d\n", __func__, __LINE__);
of_platform_depopulate(&pdev->dev);
pm_runtime_disable(&pdev->dev);
return 0;
}
static const struct of_device_id apusys_power_of_match[] = {
{ .compatible = "mediatek,apusys_power" },
{ },
};
MODULE_DEVICE_TABLE(of, apusys_power_of_match);
struct platform_driver apusys_power_driver = {
.probe = apusys_power_probe,
.remove = apusys_power_remove,
.driver = {
.name = "mediatek,apusys_power",
.of_match_table = apusys_power_of_match,
},
};
int apu_power_init(void)
{
int ret = 0;
ret = devfreq_add_governor(&agov_passive);
if (ret)
return ret;
ret = devfreq_add_governor(&agov_userspace);
if (ret)
return ret;
ret = devfreq_add_governor(&agov_constrain);
if (ret)
return ret;
ret = devfreq_add_governor(&agov_passive_pe);
if (ret)
return ret;
ret = platform_driver_register(&apu_rpc_driver);
if (ret)
return ret;
ret = platform_driver_register(&core_devfreq_driver);
if (ret)
return ret;
ret = platform_driver_register(&con_devfreq_driver);
if (ret)
return ret;
ret = platform_driver_register(&iommu_devfreq_driver);
if (ret)
return ret;
ret = platform_driver_register(&apu_cb_driver);
if (ret)
return ret;
ret = platform_driver_register(&vpu_devfreq_driver);
if (ret)
return ret;
ret = platform_driver_register(&mdla_devfreq_driver);
if (ret)
return ret;
ret = platform_driver_register(&apusys_power_driver);
if (ret)
return ret;
return 0;
}
void apu_power_exit(void)
{
int ret = 0;
platform_driver_unregister(&mdla_devfreq_driver);
platform_driver_unregister(&vpu_devfreq_driver);
platform_driver_unregister(&apu_cb_driver);
platform_driver_unregister(&apusys_power_driver);
platform_driver_unregister(&iommu_devfreq_driver);
platform_driver_unregister(&con_devfreq_driver);
platform_driver_unregister(&core_devfreq_driver);
platform_driver_unregister(&apu_rpc_driver);
ret = devfreq_remove_governor(&agov_constrain);
if (ret)
pr_info("[%s] failed remove gov %s %d\n",
__func__, agov_constrain.name, ret);
ret = devfreq_remove_governor(&agov_passive);
if (ret)
pr_info("[%s] failed remove gov %s %d\n",
__func__, agov_passive.name, ret);
ret = devfreq_remove_governor(&agov_userspace);
if (ret)
pr_info("[%s] failed remove gov %s %d\n",
__func__, agov_userspace.name, ret);
ret = devfreq_remove_governor(&agov_passive_pe);
if (ret)
pr_info("[%s] failed remove gov %s %d\n",
__func__, agov_passive_pe.name, ret);
}