934 lines
23 KiB
C
934 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2016 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/rcupdate.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/export.h>
|
|
#include <linux/module.h>
|
|
#include <linux/ktime.h>
|
|
#include <linux/io.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/types.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/pm_opp.h>
|
|
//#include <mt-plat/mtk_chip.h>
|
|
#include <linux/energy_model.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/math64.h>
|
|
|
|
/* local include */
|
|
#include "mtk_common_upower.h"
|
|
/*
|
|
* #ifdef UPOWER_NUM_LARGER
|
|
* #include "mtk_eem.h"
|
|
* #endif
|
|
*/
|
|
|
|
#if UPOWER_ENABLE_TINYSYS_SSPM
|
|
#include <sspm_reservedmem_define.h>
|
|
#endif
|
|
|
|
#ifndef EARLY_PORTING_SPOWER
|
|
#include "mtk_common_spower.h"
|
|
#endif
|
|
|
|
#ifdef UPOWER_USE_QOS_IPI
|
|
#if UPOWER_ENABLE_TINYSYS_SSPM
|
|
//#include <mtk_spm_vcore_dvfs_ipi.h>
|
|
#include <mtk_vcorefs_governor.h>
|
|
#ifdef CONFIG_MTK_QOS_FRAMEWORK
|
|
#include <mtk_qos_ipi.h>
|
|
#else
|
|
#include <helio-dvfsrc-ipi.h>
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
#if UPOWER_ENABLE
|
|
unsigned char upower_enable = 1;
|
|
#else
|
|
unsigned char upower_enable;
|
|
#endif
|
|
#ifdef UPOWER_NUM_LARGER
|
|
int iter;
|
|
#endif
|
|
|
|
|
|
/* for log print */
|
|
#define LOG_BUF_LEN 1024
|
|
#define LL_CORE_NUM 4
|
|
#define L_CORE_NUM 2
|
|
#define LKG_IDX 0
|
|
#define UPOWER_UT
|
|
/* charles add */
|
|
#define EEM_DISABLE 1
|
|
struct mtk_upower_buf {
|
|
char buf[LOG_BUF_LEN];
|
|
char *p_idx;
|
|
};
|
|
|
|
#define reset_upower_buf(upower) ((upower).p_idx = (upower).buf)
|
|
#define get_upower_buf(upower) ((upower).buf)
|
|
#define upower_buf_append(upower, fmt, args...) \
|
|
((upower).p_idx += snprintf((upower).p_idx, \
|
|
LOG_BUF_LEN - strlen((upower).buf), fmt, ##args))
|
|
|
|
/* reference to target upower tbl, ex: big upower tbl */
|
|
struct upower_tbl *upower_tbl_ref;
|
|
/* Used for rcu lock, points to all the target tables list*/
|
|
struct upower_tbl_info *new_p_tbl_infos;
|
|
/* reference to raw upower tbl lists */
|
|
struct upower_tbl_info *upower_tbl_infos;
|
|
/* sspm reserved mem info for sspm upower */
|
|
phys_addr_t upower_data_phy_addr, upower_data_virt_addr;
|
|
unsigned long long upower_data_size;
|
|
|
|
#ifdef ENABLE_UPOWER_LOG
|
|
static void print_tbl(void)
|
|
{
|
|
int i, j;
|
|
/* --------------------print static orig table -------------------------*/
|
|
/* --------------------print sram table -------------------------*/
|
|
for (i = 0; i < NR_UPOWER_BANK; i++) {
|
|
/* table size must be 512 bytes */
|
|
upower_debug("---Bank %d , tbl size %ld---\n",
|
|
i, sizeof(struct upower_tbl));
|
|
for (j = 0; j < UPOWER_OPP_NUM; j++) {
|
|
upower_debug(" cap = %llu, volt = %u, dyn = %u, lkg = {%u, %u, %u, %u, %u}\n",
|
|
upower_tbl_ref[i].row[j].cap,
|
|
upower_tbl_ref[i].row[j].volt,
|
|
upower_tbl_ref[i].row[j].dyn_pwr,
|
|
upower_tbl_ref[i].row[j].lkg_pwr[0],
|
|
upower_tbl_ref[i].row[j].lkg_pwr[1],
|
|
upower_tbl_ref[i].row[j].lkg_pwr[2],
|
|
upower_tbl_ref[i].row[j].lkg_pwr[3],
|
|
upower_tbl_ref[i].row[j].lkg_pwr[4]);
|
|
}
|
|
upower_debug(" lkg_idx, num_row: %d, %d\n",
|
|
upower_tbl_ref[i].lkg_idx,
|
|
upower_tbl_ref[i].row_num);
|
|
upower_debug("-------------------------------------------------\n");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef UPOWER_UT
|
|
void upower_ut(void)
|
|
{
|
|
struct upower_tbl_info **addr_ptr_tbl_info;
|
|
struct upower_tbl_info *ptr_tbl_info;
|
|
struct upower_tbl *ptr_tbl;
|
|
int i, j;
|
|
|
|
upower_debug("----upower_get_tbl()----\n");
|
|
/* get addr of ptr which points to upower_tbl_infos[] */
|
|
addr_ptr_tbl_info = upower_get_tbl();
|
|
/* get ptr which points to upower_tbl_infos[] */
|
|
ptr_tbl_info = *addr_ptr_tbl_info;
|
|
upower_debug("get upower tbl location = %p\n",
|
|
ptr_tbl_info[0].p_upower_tbl);
|
|
#ifdef ENABLE_UPOWER_LOG
|
|
upower_debug("ptr_tbl_info --> %p --> tbl %p (p_upower_tbl_infos --> %p)\n",
|
|
ptr_tbl_info, ptr_tbl_info[0].p_upower_tbl,
|
|
p_upower_tbl_infos);
|
|
#endif
|
|
|
|
/* print all the tables that record in upower_tbl_infos[]*/
|
|
for (i = 0; i < NR_UPOWER_BANK; i++) {
|
|
upower_debug("bank %d\n", i);
|
|
ptr_tbl = ptr_tbl_info[i].p_upower_tbl;
|
|
for (j = 0; j < UPOWER_OPP_NUM; j++) {
|
|
upower_debug(" cap = %llu, volt = %u, dyn = %u, lkg = {%u, %u, %u, %u, %u, %u}\n",
|
|
ptr_tbl->row[j].cap,
|
|
ptr_tbl->row[j].volt,
|
|
ptr_tbl->row[j].dyn_pwr,
|
|
ptr_tbl->row[j].lkg_pwr[0],
|
|
ptr_tbl->row[j].lkg_pwr[1],
|
|
ptr_tbl->row[j].lkg_pwr[2],
|
|
ptr_tbl->row[j].lkg_pwr[3],
|
|
ptr_tbl->row[j].lkg_pwr[4],
|
|
ptr_tbl->row[j].lkg_pwr[5]);
|
|
}
|
|
upower_debug(" lkg_idx, num_row, nr_idle_states: %d, %d ,%d\n",
|
|
ptr_tbl->lkg_idx, ptr_tbl->row_num,
|
|
ptr_tbl->nr_idle_states);
|
|
|
|
for (i = 0; i < NR_UPOWER_DEGREE; i++) {
|
|
upower_debug("(%d)C c0 = %lu, c1 = %lu\n",
|
|
degree_set[i],
|
|
ptr_tbl->idle_states[i][0].power,
|
|
ptr_tbl->idle_states[i][1].power);
|
|
|
|
}
|
|
}
|
|
|
|
upower_debug("@@turn_point= %d\n", upower_get_turn_point());
|
|
upower_debug("----upower_get_power()----\n");
|
|
for (i = 0; i < NR_UPOWER_BANK; i++) {
|
|
upower_debug("bank %d\n", i);
|
|
upower_debug("[dyn] %u, %u, %u, %u, %u, %u, %u, %u, %u\n",
|
|
upower_get_power(i, 0, UPOWER_DYN),
|
|
upower_get_power(i, 1, UPOWER_DYN),
|
|
upower_get_power(i, 2, UPOWER_DYN),
|
|
upower_get_power(i, 3, UPOWER_DYN),
|
|
upower_get_power(i, 4, UPOWER_DYN),
|
|
upower_get_power(i, 5, UPOWER_DYN),
|
|
upower_get_power(i, 6, UPOWER_DYN),
|
|
upower_get_power(i, 7, UPOWER_DYN),
|
|
upower_get_power(i, 15, UPOWER_DYN));
|
|
upower_debug("[lkg] %u, %u, %u, %u, %u, %u, %u, %u, %u\n",
|
|
upower_get_power(i, 0, UPOWER_LKG),
|
|
upower_get_power(i, 1, UPOWER_LKG),
|
|
upower_get_power(i, 2, UPOWER_LKG),
|
|
upower_get_power(i, 3, UPOWER_LKG),
|
|
upower_get_power(i, 4, UPOWER_LKG),
|
|
upower_get_power(i, 5, UPOWER_LKG),
|
|
upower_get_power(i, 6, UPOWER_LKG),
|
|
upower_get_power(i, 7, UPOWER_LKG),
|
|
upower_get_power(i, 15, UPOWER_LKG));
|
|
}
|
|
}
|
|
#endif
|
|
static void upower_update_dyn_pwr(void)
|
|
{
|
|
unsigned long long refPower, newVolt, refVolt, newPower;
|
|
unsigned long long temp1, temp2;
|
|
int i, j;
|
|
struct upower_tbl *tbl;
|
|
|
|
for (i = 0; i < NR_UPOWER_BANK; i++) {
|
|
tbl = upower_tbl_infos[i].p_upower_tbl;
|
|
for (j = 0; j < UPOWER_OPP_NUM; j++) {
|
|
refPower = (unsigned long long)tbl->row[j].dyn_pwr;
|
|
refVolt = (unsigned long long)tbl->row[j].volt;
|
|
newVolt = (unsigned long long)
|
|
upower_tbl_ref[i].row[j].volt;
|
|
temp1 = (refPower * newVolt * newVolt);
|
|
temp2 = (refVolt * refVolt);
|
|
#if defined(__LP64__) || defined(_LP64)
|
|
newPower = temp1 / temp2;
|
|
#else
|
|
newPower = div64_u64(temp1, temp2);
|
|
#endif
|
|
upower_tbl_ref[i].row[j].dyn_pwr = newPower;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void upower_get_p_state_lkg(unsigned int bank,
|
|
unsigned int spower_bank_id)
|
|
{
|
|
int j, k;
|
|
int degree;
|
|
unsigned int volt;
|
|
unsigned int temp;
|
|
|
|
/* get p-state lkg */
|
|
for (j = 0; j < UPOWER_OPP_NUM; j++) {
|
|
volt = (unsigned int)upower_tbl_ref[bank].row[j].volt;
|
|
for (k = 0; k < NR_UPOWER_DEGREE; k++) {
|
|
degree = degree_set[k];
|
|
/* get leakage and transfer mw to uw */
|
|
temp = mt_spower_get_leakage(
|
|
spower_bank_id,
|
|
(volt/100),
|
|
degree);
|
|
|
|
upower_tbl_ref[bank].row[j].lkg_pwr[k] = temp * 1000;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void upower_get_c_state_lkg(unsigned int bank,
|
|
unsigned int spower_bank_id, struct upower_tbl *tbl)
|
|
{
|
|
int j, k;
|
|
int degree;
|
|
unsigned int volt;
|
|
unsigned int temp;
|
|
|
|
/* get c-state lkg */
|
|
upower_tbl_ref[bank].nr_idle_states = NR_UPOWER_CSTATES;
|
|
volt = UPOWER_C1_VOLT;
|
|
for (j = 0; j < NR_UPOWER_DEGREE; j++) {
|
|
for (k = 0; k < NR_UPOWER_CSTATES; k++) {
|
|
/* if c1 state, query lkg from lkg driver */
|
|
if (k == UPOWER_C1_IDX) {
|
|
degree = degree_set[j];
|
|
/* get leakage and transfer mw to uw */
|
|
temp = mt_spower_get_leakage(
|
|
spower_bank_id,
|
|
(volt/100),
|
|
degree);
|
|
|
|
upower_tbl_ref[bank].idle_states[j][k].power =
|
|
(unsigned long)(temp * 1000);
|
|
} else {
|
|
upower_tbl_ref[bank].idle_states[j][k].power =
|
|
tbl->idle_states[j][k].power;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void upower_update_lkg_pwr(void)
|
|
{
|
|
int i;
|
|
struct upower_tbl *tbl;
|
|
int j, k;
|
|
unsigned int spower_bank_id;
|
|
/*
|
|
*#ifdef EARLY_PORTING_SPOWER
|
|
* int j, k;
|
|
*#else
|
|
*
|
|
* unsigned int spower_bank_id;
|
|
*#endif
|
|
*/
|
|
for (i = 0; i < NR_UPOWER_BANK; i++) {
|
|
tbl = upower_tbl_infos[i].p_upower_tbl;
|
|
/* modify mt3967 */
|
|
|
|
#ifdef EARLY_PORTING_SPOWER
|
|
/* get p-state lkg */
|
|
for (j = 0; j < UPOWER_OPP_NUM; j++) {
|
|
for (k = 0; k < NR_UPOWER_DEGREE; k++)
|
|
upower_tbl_ref[i].row[j].lkg_pwr[k] =
|
|
tbl->row[j].lkg_pwr[k];
|
|
}
|
|
|
|
/* get c-state lkg */
|
|
for (j = 0; j < NR_UPOWER_DEGREE; j++) {
|
|
for (k = 0; k < NR_UPOWER_CSTATES; k++)
|
|
upower_tbl_ref[i].idle_states[j][k].power =
|
|
tbl->idle_states[j][k].power;
|
|
}
|
|
#else
|
|
spower_bank_id = upower_bank_to_spower_bank(i);
|
|
|
|
/* wrong bank or LL L CLUSTER set default lkg_pwr*/
|
|
if (spower_bank_id == -1) {
|
|
|
|
for (j = 0; j < UPOWER_OPP_NUM; j++) {
|
|
for (k = 0; k < NR_UPOWER_DEGREE; k++)
|
|
upower_tbl_ref[i].row[j].lkg_pwr[k] =
|
|
tbl->row[j].lkg_pwr[k];
|
|
}
|
|
continue;
|
|
}
|
|
|
|
upower_get_p_state_lkg(i, spower_bank_id);
|
|
|
|
upower_get_c_state_lkg(i, spower_bank_id, tbl);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void upower_init_cap(void)
|
|
{
|
|
int i, j;
|
|
struct upower_tbl *tbl;
|
|
|
|
for (i = 0; i < NR_UPOWER_BANK; i++) {
|
|
tbl = upower_tbl_infos[i].p_upower_tbl;
|
|
for (j = 0; j < UPOWER_OPP_NUM; j++)
|
|
upower_tbl_ref[i].row[j].cap = tbl->row[j].cap;
|
|
}
|
|
}
|
|
|
|
static void upower_init_rownum(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NR_UPOWER_BANK; i++)
|
|
upower_tbl_ref[i].row_num = UPOWER_OPP_NUM;
|
|
}
|
|
|
|
static unsigned int eem_is_enabled(void)
|
|
{
|
|
/* #ifndef EARLY_PORTING_EEM */
|
|
#ifndef EEM_DISABLE
|
|
return mt_eem_is_enabled();
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static void upower_wait_for_eem_volt_done(void)
|
|
{
|
|
#ifndef EEM_NOT_SET_VOLT
|
|
unsigned char eem_volt_not_ready = 0;
|
|
int i;
|
|
|
|
udelay(100);
|
|
while (1) {
|
|
eem_volt_not_ready = 0;
|
|
for (i = 0; i < NR_UPOWER_BANK; i++) {
|
|
if (upower_tbl_ref[i].row[UPOWER_OPP_NUM - 1].volt == 0)
|
|
eem_volt_not_ready = 1;
|
|
}
|
|
if (!eem_volt_not_ready)
|
|
break;
|
|
/* if eem volt not ready, wait 100us */
|
|
upower_debug("wait for eem update\n");
|
|
udelay(100);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef UPOWER_NUM_LARGER
|
|
static void upower_wait_for_eem_volt_done_upn_larger(void)
|
|
{
|
|
unsigned char eem_volt_not_ready = 0;
|
|
int i;
|
|
|
|
/* ensure upower bank num does not larger than eem det num */
|
|
iter =
|
|
(int)NR_EEM_DET < (int)NR_UPOWER_BANK ? NR_EEM_DET:NR_UPOWER_BANK;
|
|
udelay(100);
|
|
while (1) {
|
|
eem_volt_not_ready = 0;
|
|
for (i = 0; i < iter; i++) {
|
|
upower_debug("tbl_ref = %d iter %d\n",
|
|
upower_tbl_ref[i].row[UPOWER_OPP_NUM - 1].volt,
|
|
iter);
|
|
if (upower_tbl_ref[i].row[UPOWER_OPP_NUM - 1].volt == 0)
|
|
eem_volt_not_ready = 1;
|
|
}
|
|
if (!eem_volt_not_ready)
|
|
break;
|
|
/* if eem volt not ready, wait 100us */
|
|
udelay(100);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void upower_init_lkgidx(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NR_UPOWER_BANK; i++)
|
|
upower_tbl_ref[i].lkg_idx = DEFAULT_LKG_IDX;
|
|
}
|
|
|
|
#ifdef UPOWER_USE_DEF_CCI_TBL
|
|
static void upower_init_volt_cci(void)
|
|
{
|
|
int j;
|
|
struct upower_tbl *tbl;
|
|
|
|
tbl = upower_tbl_infos[UPOWER_BANK_CCI].p_upower_tbl;
|
|
for (j = 0; j < UPOWER_OPP_NUM; j++)
|
|
upower_tbl_ref[UPOWER_BANK_CCI].row[j].volt = tbl->row[j].volt;
|
|
}
|
|
#endif
|
|
|
|
static void upower_init_volt(void)
|
|
{
|
|
int i, j;
|
|
struct upower_tbl *tbl;
|
|
|
|
for (i = 0; i < NR_UPOWER_BANK; i++) {
|
|
tbl = upower_tbl_infos[i].p_upower_tbl;
|
|
for (j = 0; j < UPOWER_OPP_NUM; j++)
|
|
upower_tbl_ref[i].row[j].volt = tbl->row[j].volt;
|
|
}
|
|
}
|
|
#ifdef UPOWER_NUM_LARGER
|
|
static void confirm_volt(void)
|
|
{
|
|
int i, j;
|
|
struct upower_tbl *tbl;
|
|
|
|
for (i = iter; i < NR_UPOWER_BANK; i++) {
|
|
tbl = upower_tbl_infos[i].p_upower_tbl;
|
|
for (j = 0; j < UPOWER_OPP_NUM; j++)
|
|
upower_tbl_ref[i].row[j].volt = tbl->row[j].volt;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
static int upower_update_tbl_ref(void)
|
|
{
|
|
int i;
|
|
int ret = 0;
|
|
|
|
/* To disable upower, do not update upower ptr*/
|
|
if (!upower_enable) {
|
|
upower_error("upower is disabled\n");
|
|
return 0;
|
|
}
|
|
|
|
#ifdef UPOWER_PROFILE_API_TIME
|
|
upower_get_start_time_us(UPDATE_TBL_PTR);
|
|
#endif
|
|
|
|
new_p_tbl_infos = kzalloc(sizeof(*new_p_tbl_infos)
|
|
* NR_UPOWER_BANK, GFP_KERNEL);
|
|
if (!new_p_tbl_infos) {
|
|
upower_error("Out of mem to create new_p_tbl_infos\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* upower_tbl_ref is the ptr points to table in sram */
|
|
for (i = 0; i < NR_UPOWER_BANK; i++) {
|
|
new_p_tbl_infos[i].p_upower_tbl = &upower_tbl_ref[i];
|
|
new_p_tbl_infos[i].name = upower_tbl_infos[i].name;
|
|
/* upower_debug("new_p_tbl_infos[%d].name = %s\n",
|
|
* i, new_p_tbl_infos[i].name);
|
|
*/
|
|
}
|
|
|
|
#ifdef UPOWER_RCU_LOCK
|
|
rcu_assign_pointer(p_upower_tbl_infos, new_p_tbl_infos);
|
|
/* synchronize_rcu();*/
|
|
#else
|
|
p_upower_tbl_infos = new_p_tbl_infos;
|
|
#endif
|
|
|
|
#ifdef UPOWER_PROFILE_API_TIME
|
|
upower_get_diff_time_us(UPDATE_TBL_PTR);
|
|
print_diff_results(UPDATE_TBL_PTR);
|
|
#endif
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void get_L_pwr_efficiency(void)
|
|
{
|
|
int i;
|
|
unsigned int max = 0;
|
|
unsigned int min = ~0U;
|
|
unsigned long long sum;
|
|
struct upower_tbl *tbl;
|
|
|
|
for (i = 0; i < UPOWER_OPP_NUM; i++) {
|
|
tbl = &upower_tbl_ref[UPOWER_BANK_L];
|
|
sum = (unsigned long long)(tbl->row[i].lkg_pwr[LKG_IDX] +
|
|
tbl->row[i].dyn_pwr);
|
|
#if defined(__LP64__) || defined(_LP64)
|
|
tbl->row[i].pwr_efficiency =
|
|
sum / (unsigned long long)tbl->row[i].cap;
|
|
|
|
#else
|
|
tbl->row[i].pwr_efficiency =
|
|
div64_u64(sum, (unsigned long long)tbl->row[i].cap);
|
|
#endif
|
|
|
|
upower_debug("L[%d] eff = %d dyn = %d lkg = %d cap = %d\n",
|
|
i, tbl->row[i].pwr_efficiency,
|
|
tbl->row[i].dyn_pwr,
|
|
tbl->row[i].lkg_pwr[LKG_IDX],
|
|
tbl->row[i].cap
|
|
);
|
|
|
|
if (tbl->row[i].pwr_efficiency > max)
|
|
max = tbl->row[i].pwr_efficiency;
|
|
if (tbl->row[i].pwr_efficiency < min)
|
|
min = tbl->row[i].pwr_efficiency;
|
|
}
|
|
|
|
tbl->max_efficiency = max;
|
|
tbl->min_efficiency = min;
|
|
}
|
|
|
|
static void get_LL_pwr_efficiency(void)
|
|
{
|
|
int i;
|
|
unsigned int max = 0;
|
|
unsigned int min = ~0U;
|
|
unsigned long long LL_pwr, CCI_pwr;
|
|
unsigned long long sum;
|
|
struct upower_tbl *tbl, *ctbl;
|
|
|
|
tbl = &upower_tbl_ref[UPOWER_BANK_LL];
|
|
ctbl = &upower_tbl_ref[UPOWER_BANK_CCI];
|
|
for (i = 0; i < UPOWER_OPP_NUM; i++) {
|
|
LL_pwr = (unsigned long long)(tbl->row[i].lkg_pwr[LKG_IDX] +
|
|
tbl->row[i].dyn_pwr);
|
|
CCI_pwr = (unsigned long long)(ctbl->row[i].lkg_pwr[LKG_IDX] +
|
|
ctbl->row[i].dyn_pwr);
|
|
sum = (unsigned long long)LL_CORE_NUM * LL_pwr + CCI_pwr;
|
|
#if defined(__LP64__) || defined(_LP64)
|
|
tbl->row[i].pwr_efficiency =
|
|
sum / (unsigned long long)(LL_CORE_NUM * tbl->row[i].cap);
|
|
|
|
#else
|
|
tbl->row[i].pwr_efficiency =
|
|
div64_u64(LL_CORE_NUM * LL_pwr + CCI_pwr,
|
|
(unsigned long long)(LL_CORE_NUM * tbl->row[i].cap))
|
|
#endif
|
|
|
|
upower_debug("LL[%d] eff = %d dyn = %d lkg = %d cap = %d\n",
|
|
i, tbl->row[i].pwr_efficiency,
|
|
tbl->row[i].dyn_pwr,
|
|
tbl->row[i].lkg_pwr[LKG_IDX],
|
|
tbl->row[i].cap
|
|
);
|
|
|
|
if (tbl->row[i].pwr_efficiency > max)
|
|
max = tbl->row[i].pwr_efficiency;
|
|
if (tbl->row[i].pwr_efficiency < min)
|
|
min = tbl->row[i].pwr_efficiency;
|
|
}
|
|
|
|
tbl->max_efficiency = max;
|
|
tbl->min_efficiency = min;
|
|
}
|
|
static int upower_cal_turn_point(void)
|
|
{
|
|
int i;
|
|
struct upower_tbl *L_tbl, *LL_tbl;
|
|
int tempLL;
|
|
int find_flag = 0;
|
|
|
|
L_tbl = &upower_tbl_ref[UPOWER_BANK_L];
|
|
LL_tbl = &upower_tbl_ref[UPOWER_BANK_LL];
|
|
/* calculate turn point */
|
|
for (i = UPOWER_OPP_NUM - 1; i >= 0 ; i--) {
|
|
tempLL = LL_tbl->row[i].pwr_efficiency;
|
|
upower_debug("@@LL_effi[%d] = %d , L_min_effi = %d\n",
|
|
i, tempLL, L_tbl->min_efficiency);
|
|
if (tempLL <= L_tbl->min_efficiency) {
|
|
L_tbl->turn_point = i + 1;
|
|
LL_tbl->turn_point = i + 1;
|
|
find_flag = 1;
|
|
break;
|
|
}
|
|
|
|
}
|
|
if (!find_flag) {
|
|
L_tbl->turn_point = UPOWER_OPP_NUM;
|
|
LL_tbl->turn_point = UPOWER_OPP_NUM;
|
|
i = UPOWER_OPP_NUM;
|
|
}
|
|
|
|
L_tbl->turn_point = 13;
|
|
LL_tbl->turn_point = 13;
|
|
return i;
|
|
|
|
}
|
|
|
|
#ifdef UPOWER_USE_QOS_IPI
|
|
#if UPOWER_ENABLE_TINYSYS_SSPM
|
|
void upower_send_data_ipi(phys_addr_t phy_addr, unsigned long long size)
|
|
{
|
|
struct qos_ipi_data qos_d;
|
|
|
|
qos_d.cmd = QOS_IPI_UPOWER_DATA_TRANSFER;
|
|
qos_d.u.upower_data.arg[0] = phy_addr;
|
|
qos_d.u.upower_data.arg[1] = size;
|
|
qos_ipi_to_sspm_command(&qos_d, 3);
|
|
}
|
|
|
|
void upower_dump_data_ipi(void)
|
|
{
|
|
struct qos_ipi_data qos_d;
|
|
|
|
qos_d.cmd = QOS_IPI_UPOWER_DUMP_TABLE;
|
|
qos_ipi_to_sspm_command(&qos_d, 1);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
static int __init upower_get_tbl_ref(void)
|
|
{
|
|
#if UPOWER_ENABLE_TINYSYS_SSPM
|
|
int i;
|
|
unsigned char *ptr;
|
|
#endif
|
|
|
|
#ifdef UPOWER_NOT_READY
|
|
return 0;
|
|
#endif
|
|
/* get raw upower table and target upower table location */
|
|
get_original_table();
|
|
|
|
#if UPOWER_ENABLE_TINYSYS_SSPM
|
|
/* get sspm reserved mem */
|
|
upower_data_phy_addr = sspm_reserve_mem_get_phys(UPD_MEM_ID);
|
|
upower_data_virt_addr = sspm_reserve_mem_get_virt(UPD_MEM_ID);
|
|
upower_data_size = sspm_reserve_mem_get_size(UPD_MEM_ID);
|
|
|
|
upower_debug("phy_addr = 0x%llx, virt_addr=0x%llx\n",
|
|
(unsigned long long)upower_data_phy_addr,
|
|
(unsigned long long)upower_data_virt_addr);
|
|
|
|
/* clear */
|
|
ptr = (unsigned char *)(uintptr_t)upower_data_virt_addr;
|
|
for (i = 0; i < upower_data_size; i++)
|
|
ptr[i] = 0x0;
|
|
|
|
upower_tbl_ref = (struct upower_tbl *)(uintptr_t)upower_data_virt_addr;
|
|
|
|
#ifdef UPOWER_USE_QOS_IPI
|
|
upower_send_data_ipi(upower_data_phy_addr, upower_data_size);
|
|
#else
|
|
/* send sspm reserved mem into sspm through eem's ipi (need fix) */
|
|
mt_eem_send_upower_table_ref(upower_data_phy_addr, upower_data_size);
|
|
#endif
|
|
#endif
|
|
/* upower_tbl_ref is assigned in get_original_table() if no sspm */
|
|
upower_debug("upower tbl orig location([0](%p)= %p\n",
|
|
upower_tbl_infos, upower_tbl_infos[0].p_upower_tbl);
|
|
upower_debug("upower tbl new location([0](%p)\n", upower_tbl_ref);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef UPOWER_PROFILE_API_TIME
|
|
static void profile_api(void)
|
|
{
|
|
int i, j;
|
|
|
|
upower_debug("----profile upower_get_power()----\n");
|
|
/* do 56*2 times */
|
|
for (i = 0; i < NR_UPOWER_BANK; i++) {
|
|
for (j = 0; j < UPOWER_OPP_NUM; j++) {
|
|
upower_get_power(i, j, UPOWER_DYN);
|
|
upower_get_power(i, j, UPOWER_LKG);
|
|
}
|
|
}
|
|
upower_debug("----profile upower_update_tbl_ref()----\n");
|
|
for (i = 0; i < 10; i++)
|
|
upower_update_tbl_ref();
|
|
}
|
|
#endif
|
|
|
|
static int upower_debug_proc_show(struct seq_file *m, void *v)
|
|
{
|
|
|
|
struct upower_tbl_info **addr_ptr_tbl_info;
|
|
struct upower_tbl_info *ptr_tbl_info;
|
|
struct upower_tbl *ptr_tbl;
|
|
int i, j;
|
|
|
|
/* get addr of ptr which points to upower_tbl_infos[] */
|
|
addr_ptr_tbl_info = upower_get_tbl();
|
|
/* get ptr which points to upower_tbl_infos[] */
|
|
ptr_tbl_info = *addr_ptr_tbl_info;
|
|
|
|
seq_printf(m,
|
|
"ptr_tbl_info --> %p --> tbl %p (p_upower_tbl_infos --> %p)\n",
|
|
ptr_tbl_info, ptr_tbl_info[0].p_upower_tbl, p_upower_tbl_infos);
|
|
|
|
/* print all the tables that record in upower_tbl_infos[]*/
|
|
for (i = 0; i < NR_UPOWER_BANK; i++) {
|
|
seq_printf(m, "%s\n", upower_tbl_infos[i].name);
|
|
ptr_tbl = ptr_tbl_info[i].p_upower_tbl;
|
|
for (j = 0; j < UPOWER_OPP_NUM; j++) {
|
|
seq_printf(m, " cap = %lu, volt = %u, dyn = %u,",
|
|
ptr_tbl->row[j].cap,
|
|
ptr_tbl->row[j].volt,
|
|
ptr_tbl->row[j].dyn_pwr);
|
|
seq_printf(m,
|
|
" lkg = {%u, %u, %u, %u, %u, %u} pwr_efficiency = %u\n",
|
|
ptr_tbl->row[j].lkg_pwr[0],
|
|
ptr_tbl->row[j].lkg_pwr[1],
|
|
ptr_tbl->row[j].lkg_pwr[2],
|
|
ptr_tbl->row[j].lkg_pwr[3],
|
|
ptr_tbl->row[j].lkg_pwr[4],
|
|
ptr_tbl->row[j].lkg_pwr[5],
|
|
ptr_tbl->row[j].pwr_efficiency);
|
|
}
|
|
seq_printf(m, " lkg_idx, num_row, turn_point: %d, %d, %d\n\n",
|
|
ptr_tbl->lkg_idx, ptr_tbl->row_num, ptr_tbl->turn_point);
|
|
}
|
|
|
|
#ifdef UPOWER_USE_QOS_IPI
|
|
#if UPOWER_ENABLE_TINYSYS_SSPM
|
|
upower_dump_data_ipi();
|
|
#endif
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
#define PROC_FOPS_RW(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 file_operations name ## _proc_fops = { \
|
|
.owner = THIS_MODULE, \
|
|
.open = name ## _proc_open, \
|
|
.read = seq_read, \
|
|
.llseek = seq_lseek, \
|
|
.release = single_release, \
|
|
.write = name ## _proc_write, \
|
|
}
|
|
|
|
#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 file_operations name ## _proc_fops = { \
|
|
.owner = THIS_MODULE, \
|
|
.open = name ## _proc_open, \
|
|
.read = seq_read, \
|
|
.llseek = seq_lseek, \
|
|
.release = single_release, \
|
|
}
|
|
|
|
#define PROC_ENTRY(name) {__stringify(name), &name ## _proc_fops}
|
|
/* create fops */
|
|
PROC_FOPS_RO(upower_debug);
|
|
|
|
static int create_procfs(void)
|
|
{
|
|
struct proc_dir_entry *upower_dir = NULL;
|
|
int i = 0;
|
|
|
|
struct pentry {
|
|
const char *name;
|
|
const struct file_operations *fops;
|
|
};
|
|
|
|
struct pentry upower_entries[] = {
|
|
/* {__stringify(name), &name ## _proc_fops} */
|
|
PROC_ENTRY(upower_debug),
|
|
};
|
|
|
|
/* To disable upower, do not create procfs*/
|
|
if (!upower_enable)
|
|
return 0;
|
|
|
|
/* create proc/upower node */
|
|
upower_dir = proc_mkdir("upower", NULL);
|
|
if (!upower_dir) {
|
|
upower_error("[%s] mkdir /proc/upower failed\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(upower_entries); i++) {
|
|
if (!proc_create(upower_entries[i].name,
|
|
0644,
|
|
upower_dir,
|
|
upower_entries[i].fops)) {
|
|
upower_error("[%s]: create /proc/upower/%s failed\n",
|
|
__func__,
|
|
upower_entries[i].name);
|
|
return -3;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void upower_register_perf_domain(void)
|
|
{
|
|
int retv;
|
|
int cpu = 0;
|
|
struct cpufreq_policy *policy;
|
|
struct em_data_callback em_cb = EM_DATA_CB(of_dev_pm_opp_get_cpu_power);
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
policy = cpufreq_cpu_get(cpu);
|
|
if (!policy) {
|
|
pr_notice("cpu %d get policy fail\n", cpu);
|
|
return;
|
|
}
|
|
retv = em_register_perf_domain(policy->cpus,
|
|
UPOWER_OPP_NUM, &em_cb);
|
|
cpufreq_cpu_put(policy);
|
|
}
|
|
}
|
|
|
|
static int __init upower_init(void)
|
|
{
|
|
int turn;
|
|
/* charles modify */
|
|
/* int cpu; */
|
|
struct upower_tbl_info **addr_ptr_tbl_info;
|
|
struct upower_tbl_info *ptr_tbl_info;
|
|
#ifdef UPOWER_NOT_READY
|
|
return 0;
|
|
#endif
|
|
|
|
/* PTP has no efuse, so volt will be set to orig data */
|
|
/* before upower_init_volt(), PTP has called
|
|
* upower_update_volt_by_eem()
|
|
*/
|
|
|
|
#ifdef UPOWER_UT
|
|
upower_debug("--------- (UT)before tbl ready--------------\n");
|
|
upower_ut();
|
|
#endif
|
|
|
|
/* init rownum to UPOWER_OPP_NUM*/
|
|
upower_init_rownum();
|
|
|
|
upower_init_cap();
|
|
|
|
/* apply orig volt and lkgidx, if eem is not enabled*/
|
|
if (!eem_is_enabled()) {
|
|
upower_debug("eem is not enabled\n");
|
|
upower_init_lkgidx();
|
|
upower_init_volt();
|
|
} else {
|
|
#ifdef UPOWER_USE_DEF_CCI_TBL
|
|
upower_init_volt_cci();
|
|
#endif
|
|
#ifdef UPOWER_NUM_LARGER
|
|
upower_wait_for_eem_volt_done_upn_larger();
|
|
confirm_volt();
|
|
#endif
|
|
upower_wait_for_eem_volt_done();
|
|
}
|
|
upower_update_dyn_pwr();
|
|
upower_update_lkg_pwr();
|
|
get_L_pwr_efficiency();
|
|
get_LL_pwr_efficiency();
|
|
turn = upower_cal_turn_point();
|
|
upower_register_perf_domain();
|
|
|
|
upower_debug("@@~turn point is %d\n", turn);
|
|
#ifdef UPOWER_L_PLUS
|
|
upower_update_L_plus_cap();
|
|
upower_update_L_plus_lkg_pwr();
|
|
#endif
|
|
upower_update_tbl_ref();
|
|
|
|
upower_debug("----upower_get_tbl()----\n");
|
|
/* get addr of ptr which points to upower_tbl_infos[] */
|
|
addr_ptr_tbl_info = upower_get_tbl();
|
|
/* get ptr which points to upower_tbl_infos[] */
|
|
ptr_tbl_info = *addr_ptr_tbl_info;
|
|
/*
|
|
* for_each_possible_cpu(cpu)
|
|
* init_cpu_capacity(cpu);
|
|
*/
|
|
|
|
#ifdef UPOWER_UT
|
|
upower_debug("--------- (UT)tbl ready--------------\n");
|
|
upower_ut();
|
|
#endif
|
|
|
|
#ifdef UPOWER_PROFILE_API_TIME
|
|
profile_api();
|
|
#endif
|
|
|
|
create_procfs();
|
|
|
|
/* print_tbl(); */
|
|
return 0;
|
|
}
|
|
#ifdef __KERNEL__
|
|
subsys_initcall(upower_get_tbl_ref);
|
|
late_initcall(upower_init);
|
|
#endif
|
|
MODULE_DESCRIPTION("MediaTek Unified Power Driver v1.0");
|
|
MODULE_LICENSE("GPL");
|