kernel-brax3-ubuntu-touch/drivers/misc/mediatek/power_throttling/mtk_peak_power_ctrl.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

340 lines
7.6 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2023 MediaTek Inc.
*/
#include <linux/cpufreq.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pm_qos.h>
#include <linux/proc_fs.h>
#include <linux/spinlock.h>
#include <linux/sched/clock.h>
#include <trace/events/power.h>
#include <linux/tracepoint.h>
#include <linux/kallsyms.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/nvmem-consumer.h>
#include "mtk_peak_power_ctrl.h"
#if IS_ENABLED(CONFIG_MTK_GPUFREQ_V2)
#include <mtk_gpufreq.h>
#endif
static LIST_HEAD(ppc_policy_list);
static bool mt_ppc_debug;
static bool ppc_enable;
static unsigned int g_p_active;
enum PPC_LIMIT_SOURCE {
PPC_P_ACTIVE,
PPC_LIMIT_MAX
};
static s32 limit_freq[PPC_MAX_CLUSTER_NUM] = {1800000, 2850000, 3050000,
FREQ_QOS_MAX_DEFAULT_VALUE};
static struct ppc_ctrl_t ppc_ctrl = {
.gpu_limit_state = 0,
.cpu_limit_state = 0,
.source_state = 0,
};
static void mtk_ppc_limit_cpu(unsigned int limit)
{
struct cpu_ppc_policy *ppc_policy;
s32 frequency;
unsigned int i = 0;
if (mt_ppc_debug)
pr_info("%s: limit=%u\n", __func__, limit);
list_for_each_entry(ppc_policy, &ppc_policy_list, cpu_ppc_list) {
if (limit)
frequency = limit_freq[i];
else
frequency = FREQ_QOS_MAX_DEFAULT_VALUE;
freq_qos_update_request(ppc_policy->qos_req, frequency);
i++;
}
}
static void mtk_ppc_limit_gpu(int limit)
{
#if IS_ENABLED(CONFIG_MTK_GPUFREQ_V2)
gpufreq_set_limit(TARGET_DEFAULT, LIMIT_PEAK_POWER_AP, (limit == 1) ? 981000 : 0,
GPUPPM_KEEP_IDX);
#endif
}
static unsigned int mtk_ppc_arbitrate_and_set_limit(enum PPC_LIMIT_SOURCE source,
unsigned int enable)
{
unsigned int gpu_pre_state, cpu_pre_state;
if (source >= PPC_LIMIT_MAX)
return 0;
if (!ppc_enable)
return 0;
gpu_pre_state = ppc_ctrl.gpu_limit_state;
cpu_pre_state = ppc_ctrl.cpu_limit_state;
if (enable != (ppc_ctrl.source_state >> source & 0x1))
ppc_ctrl.source_state ^= (1 << source);
ppc_ctrl.gpu_limit_state = (ppc_ctrl.source_state) ? 1 : 0;
ppc_ctrl.cpu_limit_state = ppc_ctrl.source_state & 0x1;
if (ppc_ctrl.gpu_limit_state != gpu_pre_state)
mtk_ppc_limit_gpu(ppc_ctrl.gpu_limit_state);
if (ppc_ctrl.cpu_limit_state != cpu_pre_state)
mtk_ppc_limit_cpu(ppc_ctrl.cpu_limit_state);
return (ppc_ctrl.gpu_limit_state != gpu_pre_state);
}
static int mt_ppc_debug_proc_show(struct seq_file *m, void *v)
{
if (mt_ppc_debug)
seq_puts(m, "ppc debug enabled\n");
else
seq_puts(m, "ppc debug disabled\n");
return 0;
}
static ssize_t mt_ppc_debug_proc_write
(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
char desc[32];
unsigned int len = 0;
int debug = 0;
len = (count < (sizeof(desc) - 1)) ? count : (sizeof(desc) - 1);
if (copy_from_user(desc, buffer, len))
return 0;
desc[len] = '\0';
if (kstrtoint(desc, 10, &debug) == 0) {
if (debug == 0)
mt_ppc_debug = 0;
else if (debug == 1)
mt_ppc_debug = 1;
else
pr_notice("should be [0:disable,1:enable]\n");
} else
pr_notice("should be [0:disable,1:enable]\n");
return count;
}
static int mt_p_active_proc_show(struct seq_file *m, void *v)
{
if (g_p_active)
seq_puts(m, "1\n");
else
seq_puts(m, "0\n");
return 0;
}
static ssize_t mt_p_active_proc_write
(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
char desc[32];
unsigned int len = 0;
int input = 0;
len = (count < (sizeof(desc) - 1)) ? count : (sizeof(desc) - 1);
if (copy_from_user(desc, buffer, len))
return 0;
desc[len] = '\0';
if (kstrtoint(desc, 10, &input) == 0) {
if (input == 0 && g_p_active != 0) {
g_p_active = 0;
mtk_ppc_arbitrate_and_set_limit(PPC_P_ACTIVE, g_p_active);
} else if (input == 1 && g_p_active == 0) {
g_p_active = 1;
mtk_ppc_arbitrate_and_set_limit(PPC_P_ACTIVE, g_p_active);
}
}
return count;
}
#define PROC_FOPS_RW(name) \
static int mt_ ## name ## _proc_open(struct inode *inode, struct file *file)\
{ \
return single_open(file, mt_ ## name ## _proc_show, PDE_DATA(inode));\
} \
static const struct proc_ops mt_ ## name ## _proc_fops = { \
.proc_open = mt_ ## name ## _proc_open, \
.proc_read = seq_read, \
.proc_lseek = seq_lseek, \
.proc_release = single_release, \
.proc_write = mt_ ## name ## _proc_write, \
}
#define PROC_ENTRY(name) {__stringify(name), &mt_ ## name ## _proc_fops}
PROC_FOPS_RW(ppc_debug);
PROC_FOPS_RW(p_active);
static int mt_ppc_create_procfs(void)
{
struct proc_dir_entry *dir = NULL;
int i;
struct pentry {
const char *name;
const struct proc_ops *fops;
};
const struct pentry entries[] = {
PROC_ENTRY(ppc_debug),
PROC_ENTRY(p_active),
};
dir = proc_mkdir("ppc", NULL);
if (!dir) {
pr_notice("fail to create /proc/ppc @ %s()\n", __func__);
return -ENOMEM;
}
for (i = 0; i < ARRAY_SIZE(entries); i++) {
if (!proc_create(entries[i].name, 0660, dir, entries[i].fops))
pr_notice("@%s: create /proc/ppc/%s failed\n", __func__,
entries[i].name);
}
return 0;
}
static int get_segment_id(struct platform_device *pdev)
{
unsigned int id = 0;
struct nvmem_cell *efuse_cell;
unsigned int *efuse_buf;
size_t efuse_len;
efuse_cell = nvmem_cell_get(&pdev->dev, "ppc_segment");
if (IS_ERR(efuse_cell)) {
pr_info("fail to get ppc_segment (%ld)", PTR_ERR(efuse_cell));
id = 0;
goto done;
}
efuse_buf = (unsigned int *)nvmem_cell_read(efuse_cell, &efuse_len);
nvmem_cell_put(efuse_cell);
if (IS_ERR(efuse_buf)) {
pr_info("fail to get efuse_buf (%ld)", PTR_ERR(efuse_buf));
goto done;
}
id = (*efuse_buf & 0xFF);
kfree(efuse_buf);
done:
return (id == 3);
}
static int ppc_probe(struct platform_device *pdev)
{
struct cpufreq_policy *policy;
struct cpu_ppc_policy *ppc_policy;
struct freq_qos_request *req;
unsigned int i;
int cpu, ret = 0;
mt_ppc_create_procfs();
ppc_enable = get_segment_id(pdev);
if (!ppc_enable) {
pr_info("ppc disable due to segment\n");
return 0;
}
for_each_possible_cpu(cpu) {
policy = cpufreq_cpu_get(cpu);
if (!policy)
continue;
if (policy->cpu == cpu) {
ppc_policy = kzalloc(sizeof(*ppc_policy), GFP_KERNEL);
if (!ppc_policy)
return -ENOMEM;
i = cpufreq_table_count_valid_entries(policy);
if (!i) {
pr_info("%s: CPUFreq table not found or has no valid entries\n",
__func__);
kfree(ppc_policy);
return -ENODEV;
}
req = kzalloc(sizeof(struct freq_qos_request), GFP_KERNEL);
if (!req) {
kfree(ppc_policy);
return -ENODEV;
}
ppc_policy->qos_req = req;
ppc_policy->policy = policy;
ppc_policy->cpu = cpu;
ret = freq_qos_add_request(&policy->constraints,
ppc_policy->qos_req, FREQ_QOS_MAX,
FREQ_QOS_MAX_DEFAULT_VALUE);
if (ret < 0) {
pr_notice("%s: Fail to add freq constraint (%d)\n",
__func__, ret);
kfree(ppc_policy);
kfree(req);
return ret;
}
list_add_tail(&ppc_policy->cpu_ppc_list, &ppc_policy_list);
}
}
return ret;
}
static int ppc_remove(struct platform_device *pdev)
{
struct cpu_ppc_policy *ppc_policy, *ppc_policy_t;
mtk_ppc_limit_cpu(0);
mtk_ppc_limit_gpu(0);
list_for_each_entry_safe(ppc_policy, ppc_policy_t, &ppc_policy_list, cpu_ppc_list) {
cpufreq_cpu_put(ppc_policy->policy);
list_del(&ppc_policy->cpu_ppc_list);
kfree(ppc_policy);
}
return 0;
}
static const struct of_device_id ppc_of_match[] = {
{ .compatible = "mediatek,mt6985-ppc", },
{},
};
MODULE_DEVICE_TABLE(of, ppc_of_match);
static struct platform_driver ppc_driver = {
.probe = ppc_probe,
.remove = ppc_remove,
.driver = {
.name = "mtk-peak-power-ctrl",
.of_match_table = ppc_of_match,
},
};
module_platform_driver(ppc_driver);
MODULE_AUTHOR("Samuel Hsieh");
MODULE_DESCRIPTION("MTK peak power control");
MODULE_LICENSE("GPL");