// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2015 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #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");