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

716 lines
19 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2015 MediaTek Inc.
*/
#include <linux/proc_fs.h>
#include <linux/debugfs.h>
#include <linux/export.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/nvmem-consumer.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#define SP_TAG "[Power/spower] "
#define SPOWERTABLE 0
#define SPOWER_LOG_NONE 0
#define SPOWER_LOG_WITH_PRINTK 1
#define SPOWER_LOG_PRINT SPOWER_LOG_NONE
#define SPOWER_ERR(fmt, args...)
#define SPOWER_INFO(fmt, args...)
#define VSIZE 14
#define TSIZE 22
#define MAX_TABLE_SIZE 3
#define trow(tab, ti) ((tab)->trow[ti])
#define mA(tab, vi, ti) ((tab)->trow[ti].mA[vi])
#define mV(tab, vi) ((tab)->vrow[0].mV[vi])
#define deg(tab, ti) ((tab)->trow[ti].deg)
#define vsize(tab) ((tab)->vsize)
#define tsize(tab) ((tab)->tsize)
#define tab_validate(tab) (!!(tab) && (tab)->data != NULL)
struct spower_raw_t {
int vsize;
int tsize;
int table_size;
int *table[3];
unsigned int devinfo_domain;
unsigned int spower_id;
unsigned int leakage_id;
unsigned int instance;
bool print_leakage;
};
struct voltage_row_s {
int mV[VSIZE];
};
struct temperature_row_s {
int deg;
int mA[VSIZE];
};
struct sptab_s {
int vsize;
int tsize;
int *data;
struct voltage_row_s *vrow;
struct temperature_row_s *trow;
unsigned int devinfo_domain;
unsigned int spower_id;
unsigned int leakage_id;
unsigned int instance;
bool print_leakage;
};
struct sptab_list {
struct sptab_s tab_raw[MAX_TABLE_SIZE];
};
struct spower_leakage_info {
const char *name;
unsigned int devinfo_idx;
unsigned int devinfo_offset;
unsigned int value;
unsigned int v_of_fuse;
int t_of_fuse;
unsigned int instance;
};
struct spower_leakage_info *spower_lkg_info;
struct spower_raw_t *spower_raw;
static struct sptab_s *sptab;
static char static_power_buf[128];
static char static_power_buf_precise[128];
static const int devinfo_table[] = {
3539, 492, 1038, 106, 231, 17, 46, 2179,
4, 481, 1014, 103, 225, 17, 45, 2129,
3, 516, 1087, 111, 242, 19, 49, 2282,
4, 504, 1063, 108, 236, 18, 47, 2230,
4, 448, 946, 96, 210, 15, 41, 1986,
2, 438, 924, 93, 205, 14, 40, 1941,
2, 470, 991, 101, 220, 16, 43, 2080,
3, 459, 968, 98, 215, 16, 42, 2033,
3, 594, 1250, 129, 279, 23, 57, 2621,
6, 580, 1221, 126, 273, 22, 56, 2561,
6, 622, 1309, 136, 293, 24, 60, 2745,
7, 608, 1279, 132, 286, 23, 59, 2683,
6, 541, 1139, 117, 254, 20, 51, 2390,
5, 528, 1113, 114, 248, 19, 50, 2335,
4, 566, 1193, 123, 266, 21, 54, 2503,
5, 553, 1166, 120, 260, 21, 53, 2446,
5, 338, 715, 70, 157, 9, 29, 1505,
3153, 330, 699, 69, 153, 9, 28, 1470,
3081, 354, 750, 74, 165, 10, 31, 1576,
3302, 346, 732, 72, 161, 10, 30, 1540,
3227, 307, 652, 63, 142, 8, 26, 1371,
2875, 300, 637, 62, 139, 7, 25, 1340,
2809, 322, 683, 67, 149, 8, 27, 1436,
3011, 315, 667, 65, 146, 8, 26, 1404,
2942, 408, 862, 86, 191, 13, 37, 1811,
1, 398, 842, 84, 186, 12, 36, 1769,
1, 428, 903, 91, 200, 14, 39, 1896,
2, 418, 882, 89, 195, 13, 38, 1853,
2, 371, 785, 78, 173, 11, 33, 1651,
3458, 363, 767, 76, 169, 10, 32, 1613,
3379, 389, 823, 82, 182, 12, 35, 1729,
1, 380, 804, 80, 177, 11, 34, 1689,
};
#if SPOWERTABLE
static inline void spower_tab_construct(struct sptab_s *tab,
struct spower_raw_t *raw, unsigned int id)
{
int i;
struct sptab_s *ptab = (struct sptab_s *)tab;
for (i = 0; i < raw->table_size; i++) {
ptab->vsize = raw->vsize;
ptab->tsize = raw->tsize;
ptab->data = raw->table[i];
ptab->vrow = (struct voltage_row_s *)ptab->data;
ptab->trow = (struct temperature_row_s *)(ptab->data + ptab->vsize);
ptab->devinfo_domain = raw->devinfo_domain;
ptab->spower_id = id;
ptab->leakage_id = raw->leakage_id;
ptab->instance = raw->instance;
ptab->print_leakage = raw->print_leakage;
ptab++;
}
}
#endif
int interpolate(int x1, int x2, int x3, int y1, int y2)
{
if (x1 == x2)
return (y1 + y2) / 2;
#if defined(__LP64__) || defined(_LP64)
return (long long)(x3 - x1) * (long long)(y2 - y1) /
(long long)(x2 - x1) +
y1;
#else
return div64_s64((long long)(x3 - x1) * (long long)(y2 - y1),
(long long)(x2 - x1)) +
y1;
#endif
}
int interpolate_2d(struct sptab_s *tab, int v1, int v2, int t1, int t2,
int voltage, int degree)
{
int c1, c2, p1, p2, p;
if ((v1 == v2) && (t1 == t2)) {
p = mA(tab, v1, t1);
} else if (v1 == v2) {
c1 = mA(tab, v1, t1);
c2 = mA(tab, v1, t2);
p = interpolate(deg(tab, t1), deg(tab, t2), degree, c1, c2);
} else if (t1 == t2) {
c1 = mA(tab, v1, t1);
c2 = mA(tab, v2, t1);
p = interpolate(mV(tab, v1), mV(tab, v2), voltage, c1, c2);
} else {
c1 = mA(tab, v1, t1);
c2 = mA(tab, v1, t2);
p1 = interpolate(deg(tab, t1), deg(tab, t2), degree, c1, c2);
c1 = mA(tab, v2, t1);
c2 = mA(tab, v2, t2);
p2 = interpolate(deg(tab, t1), deg(tab, t2), degree, c1, c2);
p = interpolate(mV(tab, v1), mV(tab, v2), voltage, p1, p2);
}
return p;
}
void interpolate_table(struct sptab_s *spt, int c1, int c2, int c3,
struct sptab_s *tab1, struct sptab_s *tab2)
{
int v, t;
/* avoid divid error, if we have bad raw data table */
if (unlikely(c1 == c2)) {
*spt = *tab1;
SPOWER_INFO("sptab equal to tab1:%d/%d\n", c1, c3);
} else {
SPOWER_INFO("make sptab %d, %d, %d\n", c1, c2, c3);
for (t = 0; t < tsize(spt); t++) {
for (v = 0; v < vsize(spt); v++) {
int *p = &mA(spt, v, t);
p[0] = interpolate(c1, c2, c3, mA(tab1, v, t),
mA(tab2, v, t));
if (v == 0 || v == vsize(spt) - 1)
SPOWER_INFO("ma1, ma2=%d, %d, %d\n",
mA(tab1, v, t),
mA(tab2, v, t), p[0]);
}
SPOWER_INFO("\n");
}
SPOWER_INFO("make sptab done!\n");
}
}
int sptab_lookup(struct sptab_s *tab, int voltage, int degree)
{
int x1, x2, y1, y2, i;
int mamper;
/* lookup voltage */
for (i = 0; i < vsize(tab); i++) {
if (voltage <= mV(tab, i))
break;
}
if (unlikely(voltage == mV(tab, i))) {
x1 = x2 = i;
} else if (unlikely(i == vsize(tab))) {
x1 = vsize(tab) - 2;
x2 = vsize(tab) - 1;
} else if (i == 0) {
x1 = 0;
x2 = 1;
} else {
x1 = i - 1;
x2 = i;
}
/* lookup degree */
for (i = 0; i < tsize(tab); i++) {
if (degree <= deg(tab, i))
break;
}
if (unlikely(degree == deg(tab, i))) {
y1 = y2 = i;
} else if (unlikely(i == tsize(tab))) {
y1 = tsize(tab) - 2;
y2 = tsize(tab) - 1;
} else if (i == 0) {
y1 = 0;
y2 = 1;
} else {
y1 = i - 1;
y2 = i;
}
mamper = interpolate_2d(tab, x1, x2, y1, y2, voltage, degree);
/*
* SPOWER_INFO("x1=%d, x2=%d, y1=%d, y2=%d\n", x1, x2, y1, y2);
* SPOWER_INFO("sptab_lookup-volt=%d, deg=%d, lkg=%d\n",
* voltage, degree, mamper);
*/
return mamper;
}
int mtk_spower_make_table(struct sptab_s *spt, int voltage, int degree,
unsigned int id, struct sptab_list *all_tab[], int n_domain)
{
int i, j;
struct sptab_s *tab[MAX_TABLE_SIZE], *tab1, *tab2, *tspt;
int wat; /* leakage that reads from efuse */
int devinfo_domain;
int c[MAX_TABLE_SIZE] = {0};
int temp;
/** FIXME, test only; please read efuse to assign. **/
/* wat = 80; */
/* voltage = 1150; */
/* degree = 30; */
SPOWER_INFO("spower_raw->table_size : %d\n", spower_raw->table_size);
/* find out target domain's 3 raw table */
for (i = 0; i < spower_raw->table_size; i++)
tab[i] = &(all_tab[id]->tab_raw[i]);
/* get leakage that reads from efuse */
wat = spower_lkg_info[tab[0]->leakage_id].value / 1000;
/** lookup tables which the chip type locates to **/
for (i = 0; i < spower_raw->table_size; i++) {
devinfo_domain = tab[i]->devinfo_domain;
SPOWER_INFO("devinfo_domain : 0x%x\n", devinfo_domain);
//for (j = 0; j < MTK_SPOWER_MAX; j++) {
for (j = 0; j < n_domain; j++) {
/* get table of reference bank, and look up target in
* that table
*/
if (devinfo_domain & BIT(j)) {
temp = (sptab_lookup(&(all_tab[j]->tab_raw[i]),
voltage, degree));
/* SPOWER_INFO("cal table %d lkg %d\n", j,
* temp);
*/
c[i] += (temp *
all_tab[j]->tab_raw[i].instance) >>
10;
/* SPOWER_INFO("total lkg %d\n", c[i]); */
}
}
SPOWER_INFO("done-->get c=%d\n", c[i]);
if (wat >= c[i])
break;
}
/** FIXME,
* There are only 2 tables are used to interpolate to form SPTAB.
* Thus, sptab takes use of the container which raw data is not used
*anymore.
**/
if (i == spower_raw->table_size) {
i = spower_raw->table_size - 1;
/** above all **/
#if defined(EXTER_POLATION)
tab1 = tab[spower_raw->table_size - 2];
tab2 = tab[spower_raw->table_size - 1];
/** occupy the free container**/
tspt = tab[spower_raw->table_size - 3];
#else /* #if defined(EXTER_POLATION) */
tspt = tab1 = tab2 = tab[spower_raw->table_size - 1];
#endif /* #if defined(EXTER_POLATION) */
SPOWER_INFO("sptab max tab:%d/%d\n", wat, c[i]);
} else if (i == 0) {
#if defined(EXTER_POLATION)
/** below all **/
tab1 = tab[0];
tab2 = tab[1];
/** occupy the free container**/
tspt = tab[2];
#else /* #if defined(EXTER_POLATION) */
tspt = tab1 = tab2 = tab[0];
#endif /* #if defined(EXTER_POLATION) */
SPOWER_INFO("sptab min tab:%d/%d\n", wat, c[i]);
} else if (wat == c[i]) {
/** just match **/
tab1 = tab2 = tab[i];
/** pointer duplicate **/
tspt = tab1;
SPOWER_INFO("sptab equal to tab:%d/%d\n", wat, c[i]);
} else {
/** anyone **/
tab1 = tab[i - 1];
tab2 = tab[i];
/** occupy the free container**/
tspt = tab[(i + 1) % spower_raw->table_size];
SPOWER_INFO("sptab interpolate tab:%d/%d, i:%d\n", wat, c[i],
i);
}
if (wat == 0) {
/* force mc50 */
tab1 = tab2 = tab[1];
tspt = tab1;
SPOWER_INFO("@@~ force mc50\n");
}
/** sptab needs to interpolate 2 tables. **/
if (tab1 != tab2)
interpolate_table(tspt, c[i - 1], c[i], wat, tab1, tab2);
/** update to global data **/
*spt = *tspt;
return 0;
}
static int static_power_show(struct seq_file *s, void *unused)
{
seq_printf(s, "%s", static_power_buf);
return 0;
}
static int static_power_open(struct inode *inode, struct file *file)
{
return single_open(file, static_power_show, NULL);
}
static const struct file_operations static_power_operations = {
.open = static_power_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int static_power_precise_show(struct seq_file *s, void *unused)
{
seq_printf(s, "%s", static_power_buf_precise);
return 0;
}
static int static_power_precise_open(struct inode *inode, struct file *file)
{
return single_open(file, static_power_precise_show, NULL);
}
static const struct file_operations static_power_precise_operations = {
.open = static_power_precise_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
#define PROC_FOPS_RO(name) \
static int name ## _proc_open(struct inode *inode, \
struct file *file) \
{ \
return single_open(file, name ## _proc_show, \
PDE_DATA(inode)); \
} \
static const struct proc_ops name ## _proc_fops = { \
.proc_open = name ## _proc_open, \
.proc_read = seq_read, \
.proc_lseek = seq_lseek, \
.proc_release = single_release, \
}
#define PROC_ENTRY(name) {__stringify(name), &name ## _proc_fops}
static int spower_lkg_proc_show(struct seq_file *m, void *v)
{
seq_printf(m, "%s", static_power_buf);
return 0;
}
PROC_FOPS_RO(spower_lkg);
int spower_procfs_init(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(spower_lkg),
};
dir = proc_mkdir("leakage", NULL);
if (!dir) {
pr_notice("fail to create /proc/leakage @ %s()\n",
__func__);
return -ENOMEM;
}
for (i = 0; i < ARRAY_SIZE(entries); i++) {
if (!proc_create
(entries[i].name, 0664, dir, entries[i].fops))
pr_notice("%s(), create /proc/leakage/%s failed\n",
__func__, entries[i].name);
}
return 0;
}
static const struct of_device_id mtk_static_power_of_match[] = {
{ .compatible = "mediatek,mtk-static-power", },
{},
};
static int mt_spower_init(struct platform_device *pdev)
{
int devinfo = 0;
unsigned int temp_lkg;
struct nvmem_device *nvmem_dev;
int i;
unsigned int v_of_fuse;
int t_of_fuse;
unsigned int idx = 0;
char *p_buf = static_power_buf;
char *p_buf_precise = static_power_buf_precise;
int n_domain = 0;
struct device_node *node = pdev->dev.of_node;
const char *domain;
u32 value[6];
int ret;
#if SPOWERTABLE
int *spower_value_0, *spower_value_1, *spower_value_2;
struct sptab_list **tab;
const char *spower;
char cell_name[25];
#endif
nvmem_dev = nvmem_device_get(&pdev->dev, "mtk_efuse");
ret = IS_ERR(nvmem_dev);
if (ret) {
ret = PTR_ERR(nvmem_dev);
return ret;
}
n_domain = of_property_count_strings(node, "domain");
spower_lkg_info = kmalloc_array(n_domain, sizeof(struct spower_leakage_info), GFP_KERNEL);
#if SPOWERTABLE
tab = kmalloc_array(n_domain, sizeof(struct sptab_list *), GFP_KERNEL);
sptab = kmalloc_array(n_domain, sizeof(struct sptab_s), GFP_KERNEL);
#endif
spower_raw = kmalloc_array(n_domain, sizeof(struct spower_raw_t), GFP_KERNEL);
for (i = 0; i < n_domain; i++) {
#if SPOWERTABLE
tab[i] = kmalloc(sizeof(struct sptab_list), GFP_KERNEL);
#endif
ret = of_property_read_string_index(node, "domain", i, &domain);
ret = of_property_read_u32_array(node, domain, value, ARRAY_SIZE(value));
#if SPOWERTABLE
ret = of_property_read_string_index(node, "spower", i, &spower);
spower_value_0 = kmalloc(sizeof(int)*(VSIZE*TSIZE+VSIZE+TSIZE), GFP_KERNEL);
memset(cell_name, '\0', sizeof(cell_name));
ret = snprintf(cell_name, sizeof(cell_name), "%s_%d", spower, 0);
ret = of_property_read_u32_array(node, cell_name, spower_value_0,
VSIZE*TSIZE+VSIZE+TSIZE);
spower_value_1 = kmalloc(sizeof(int)*(VSIZE*TSIZE+VSIZE+TSIZE), GFP_KERNEL);
memset(cell_name, '\0', sizeof(cell_name));
ret = snprintf(cell_name, sizeof(cell_name), "%s_%d", spower, 1);
ret = of_property_read_u32_array(node, cell_name, spower_value_1,
VSIZE*TSIZE+VSIZE+TSIZE);
spower_value_2 = kmalloc(sizeof(int)*(VSIZE*TSIZE+VSIZE+TSIZE), GFP_KERNEL);
memset(cell_name, '\0', sizeof(cell_name));
ret = snprintf(cell_name, sizeof(cell_name), "%s_%d", spower, 2);
ret = of_property_read_u32_array(node, cell_name, spower_value_2,
VSIZE*TSIZE+VSIZE+TSIZE);
#endif
spower_raw[i].vsize = VSIZE;
spower_raw[i].tsize = TSIZE;
spower_raw[i].table_size = MAX_TABLE_SIZE;
#if SPOWERTABLE
spower_raw[i].table[0] = spower_value_0;
spower_raw[i].table[1] = spower_value_1;
spower_raw[i].table[2] = spower_value_2;
#endif
spower_raw[i].devinfo_domain = BIT(i);
spower_raw[i].leakage_id = i;
spower_raw[i].instance = value[4];
spower_raw[i].print_leakage = true;
spower_lkg_info[i].name = domain;
spower_lkg_info[i].devinfo_offset = value[1];
spower_lkg_info[i].v_of_fuse = value[2];
spower_lkg_info[i].t_of_fuse = value[3];
spower_lkg_info[i].instance = value[4];
nvmem_device_read(nvmem_dev, value[0], sizeof(__u32), &devinfo);
temp_lkg = (devinfo & spower_lkg_info[i].devinfo_offset)
>> find_first_bit((unsigned long *)&spower_lkg_info[i].devinfo_offset, 32);
if (temp_lkg != 0) {
temp_lkg = devinfo_table[temp_lkg];
spower_lkg_info[i].value = temp_lkg * spower_lkg_info[i].v_of_fuse;
} else
spower_lkg_info[i].value = 0;
}
#if SPOWERTABLE
SPOWER_INFO("spower table construct\n");
for (i = 0; i < n_domain; i++)
spower_tab_construct(tab[i]->tab_raw, &spower_raw[i], i);
#endif
for (idx = 0; idx < n_domain; idx++) {
v_of_fuse = spower_lkg_info[idx].v_of_fuse;
t_of_fuse = spower_lkg_info[idx].t_of_fuse;
#if SPOWERTABLE
mtk_spower_make_table(&sptab[i], v_of_fuse, t_of_fuse,
(unsigned int)i, tab, n_domain);
#endif
p_buf += sprintf(p_buf, "%d/",
(spower_lkg_info[idx].value / 1000 /
spower_lkg_info[idx].instance));
p_buf_precise += sprintf(p_buf_precise, "%d.%d/",
DIV_ROUND_CLOSEST(spower_lkg_info[idx].value,
spower_lkg_info[idx].instance * 100) / 10,
DIV_ROUND_CLOSEST(spower_lkg_info[idx].value,
spower_lkg_info[idx].instance *
100) % 10
);
}
p_buf += sprintf(p_buf, "\n");
p_buf_precise += sprintf(p_buf_precise, "\n");
/* print static_power_buf and generate debugfs node */
SPOWER_ERR("%s", static_power_buf);
debugfs_create_file("static_power", S_IFREG | 0400, NULL, NULL,
&static_power_operations);
SPOWER_ERR("%s", static_power_buf_precise);
debugfs_create_file("static_power_precise", S_IFREG | 0400, NULL, NULL,
&static_power_precise_operations);
spower_procfs_init();
#if SPOWERTABLE
for (i = 0; i < n_domain; i++)
kfree(tab[i]);
#endif
return 0;
}
static struct platform_driver mtk_static_power_driver = {
.remove = NULL,
.shutdown = NULL,
.probe = mt_spower_init,
.suspend = NULL,
.resume = NULL,
.driver = {
.name = "mtk-static-power",
.of_match_table = mtk_static_power_of_match,
},
};
static int __init mtk_static_power_init(void)
{
int err = 0;
err = platform_driver_register(&mtk_static_power_driver);
if (err)
return err;
return 0;
}
static void __exit mtk_static_power_exit(void)
{
}
/* return 0, means sptab is not yet ready. */
/* vol unit should be mv */
int mt_spower_get_leakage(int dev, unsigned int vol, int deg)
{
int ret;
if (dev < 0 || !tab_validate(&sptab[dev]))
return 0;
if (vol > mV(&sptab[dev], VSIZE - 1))
vol = mV(&sptab[dev], VSIZE - 1);
else if (vol < mV(&sptab[dev], 0))
vol = mV(&sptab[dev], 0);
if (deg > deg(&sptab[dev], TSIZE - 1))
deg = deg(&sptab[dev], TSIZE - 1);
else if (deg < deg(&sptab[dev], 0))
deg = deg(&sptab[dev], 0);
ret = sptab_lookup(&sptab[dev], (int)vol, deg) >> 10;
SPOWER_INFO("%s-dev=%d,volt=%d, deg=%d, lkg=%d\n", __func__,
dev, vol, deg, ret);
return ret;
}
EXPORT_SYMBOL(mt_spower_get_leakage);
int mt_spower_get_leakage_uW(int dev, unsigned int vol, int deg)
{
int ret;
if (dev < 0 || !tab_validate(&sptab[dev]))
return 0;
if (vol > mV(&sptab[dev], VSIZE - 1))
vol = mV(&sptab[dev], VSIZE - 1);
else if (vol < mV(&sptab[dev], 0))
vol = mV(&sptab[dev], 0);
if (deg > deg(&sptab[dev], TSIZE - 1))
deg = deg(&sptab[dev], TSIZE - 1);
else if (deg < deg(&sptab[dev], 0))
deg = deg(&sptab[dev], 0);
ret = sptab_lookup(&sptab[dev], (int)vol, deg);
SPOWER_INFO("%s-dev=%d,volt=%d, deg=%d, lkg=%d\n", __func__,
dev, vol, deg, ret);
return ret;
}
EXPORT_SYMBOL(mt_spower_get_leakage_uW);
int mt_spower_get_efuse_lkg(int dev)
{
return spower_lkg_info[dev].value / 1000;
}
EXPORT_SYMBOL(mt_spower_get_efuse_lkg);
module_init(mtk_static_power_init);
module_exit(mtk_static_power_exit);
MODULE_DESCRIPTION("MediaTek Leakage Driver");
MODULE_LICENSE("GPL");