| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2020 MediaTek Inc. |
| * Author: Samuel Hsieh <samuel.hsieh@mediatek.com> |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/of_device.h> |
| #include <linux/proc_fs.h> |
| #include <linux/seq_file.h> |
| #include "mtk_pbm.h" |
| #include "mtk_mdpm.h" |
| #include "mtk_mdpm_platform.h" |
| #include "mtk_mdpm_platform_data.h" |
| #include "mtk_mdpm_platform_table.h" |
| #include "mtk_ccci_common.h" |
| |
| static u32 cnted_share_mem[SHARE_MEM_SIZE]; |
| static u32 rfhw_sel; |
| static int mt_mdpm_debug; |
| |
| static struct md_power_status mdpm_power_sta; |
| |
| #if IS_ENABLED(CONFIG_MTK_ECCCI_DRIVER) |
| static u32 *dbm_share_mem; |
| static bool md1_ccci_ready; |
| #endif |
| |
| static int section_shift[DBM_SECTION_NUM] = { |
| DBM_SECTION_1, DBM_SECTION_2, DBM_SECTION_3, |
| DBM_SECTION_4, DBM_SECTION_5, DBM_SECTION_6, |
| DBM_SECTION_7, DBM_SECTION_8, DBM_SECTION_9, |
| DBM_SECTION_10, DBM_SECTION_11, DBM_SECTION_12}; |
| |
| static struct tx_power *mdpm_tx_pwr; |
| static struct mdpm_scenario *mdpm_scen; |
| static int *scen_priority; |
| |
| void init_md_section_level(enum pbm_kicker kicker, u32 *share_mem) |
| { |
| if (!mdpm_tx_pwr || !mdpm_scen || !scen_priority) { |
| pr_notice("MD power table is empty\n"); |
| return; |
| } |
| #if IS_ENABLED(CONFIG_MTK_ECCCI_DRIVER) |
| if (share_mem == NULL) { |
| pr_info_ratelimited("can't get dbm share memory\n"); |
| return; |
| } |
| #ifdef DBM_RESERVE_OFFSET |
| share_mem += DBM_RESERVE_OFFSET; |
| #endif |
| dbm_share_mem = share_mem; |
| if (kicker == KR_MD1) { |
| init_md1_section_level(dbm_share_mem); |
| init_version_check(dbm_share_mem); |
| md1_ccci_ready = 1; |
| } else |
| pr_notice("unknown MD kicker: %d\n", kicker); |
| #else |
| return; |
| #endif |
| } |
| EXPORT_SYMBOL(init_md_section_level); |
| |
| int get_md1_power(enum mdpm_power_type power_type, bool need_update) |
| { |
| #if IS_ENABLED(CONFIG_MTK_ECCCI_DRIVER) |
| u32 share_reg; |
| enum md_scenario scenario; |
| int scenario_power, tx_power; |
| |
| if (!mdpm_tx_pwr || !mdpm_scen || !scen_priority) { |
| pr_notice("[md1_power] MD power table is empty\n"); |
| return 0; |
| } |
| |
| if (power_type >= POWER_TYPE_NUM || |
| power_type < 0) { |
| pr_notice("[md1_power] invalid power_type=%d\n", |
| power_type); |
| return 0; |
| } |
| |
| if (need_update == false) |
| return mdpm_power_sta.total_power; |
| |
| memset((void *)&mdpm_power_sta, 0, sizeof(struct md_power_status)); |
| |
| if (!md1_ccci_ready) { |
| pr_info_ratelimited("%s: md1_ccci_ready=%d\n", __func__, md1_ccci_ready); |
| return MAX_MD1_POWER; |
| } |
| |
| share_reg = get_md1_status_reg(); |
| if (dbm_share_mem == NULL) |
| return MAX_MD1_POWER; |
| |
| #ifdef GET_MD_SCEANRIO_BY_SHARE_MEMORY |
| scenario = get_md1_scenario_by_shm(dbm_share_mem); |
| #else |
| scenario = get_md1_scenario(share_reg, power_type); |
| #endif |
| scenario_power = get_md1_scenario_power(scenario, power_type, |
| &mdpm_power_sta); |
| |
| tx_power = get_md1_tx_power(scenario, dbm_share_mem, power_type, |
| &mdpm_power_sta); |
| |
| if (mt_mdpm_debug) |
| pr_info("[md1_power] scenario_power=%d tx_power=%d total=%d\n", |
| scenario_power, tx_power, scenario_power + tx_power); |
| |
| return scenario_power + tx_power; |
| #else |
| return 0; |
| #endif /* CONFIG_MTK_ECCCI_DRIVER */ |
| } |
| EXPORT_SYMBOL(get_md1_power); |
| |
| static int mt_mdpm_debug_proc_show(struct seq_file *m, void *v) |
| { |
| if (mt_mdpm_debug) |
| seq_printf(m, "mdpm debug enabled mt_mdpm_debug=%d\n", |
| mt_mdpm_debug); |
| else |
| seq_puts(m, "mdpm debug disabled\n"); |
| |
| return 0; |
| } |
| |
| /* |
| * enable debug message |
| */ |
| static ssize_t mt_mdpm_debug_proc_write |
| (struct file *file, const char __user *buffer, size_t count, loff_t *data) |
| { |
| char desc[32]; |
| int len = 0; |
| int debug = 0; |
| |
| len = (count < (sizeof(desc) - 1)) ? count : (sizeof(desc) - 1); |
| if (copy_from_user(desc, buffer, len)) |
| return 0; |
| |
| len = (len < 0) ? 0 : len; |
| desc[len] = '\0'; |
| |
| /* if (sscanf(desc, "%d", &debug) == 1) { */ |
| if (kstrtoint(desc, 10, &debug) == 0) { |
| if (debug >= 0 && debug <= 2) |
| mt_mdpm_debug = debug; |
| else |
| pr_notice("should be [0:disable, 1,2:enable level]\n"); |
| } else |
| pr_notice("should be [0:disable, 1,2:enable level]\n"); |
| |
| return count; |
| } |
| |
| static int mt_mdpm_power_proc_show(struct seq_file *m, void *v) |
| { |
| seq_printf(m, "MAX power: scenario=%dmW dbm=%dmW total=%dmW\n scenario name=%s(%d) section=%d, rat=%d, power_type=%d\n", |
| mdpm_power_sta.scanario_power, mdpm_power_sta.tx_power, |
| mdpm_power_sta.total_power, |
| mdpm_power_sta.scenario_name, mdpm_power_sta.scenario_id, |
| mdpm_power_sta.dbm_section, mdpm_power_sta.rat, |
| mdpm_power_sta.power_type); |
| |
| return 0; |
| } |
| |
| #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_FOPS_RO(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, \ |
| } |
| |
| #define PROC_ENTRY(name) {__stringify(name), &mt_ ## name ## _proc_fops} |
| |
| PROC_FOPS_RW(mdpm_debug); |
| PROC_FOPS_RO(mdpm_power); |
| |
| static int mt_mdpm_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(mdpm_debug), |
| PROC_ENTRY(mdpm_power), |
| }; |
| |
| dir = proc_mkdir("mdpm", NULL); |
| |
| if (!dir) { |
| pr_notice("fail to create /proc/mdpm @ %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/mdpm/%s failed\n", __func__, |
| entries[i].name); |
| } |
| |
| return 0; |
| } |
| |
| static int get_md1_scenario_internal(u32 share_reg) |
| { |
| int hit = -1; |
| int i = 0; |
| |
| #ifdef GET_MD_SCEANRIO_BY_SHARE_MEMORY |
| for (i = 0; i < SCENARIO_NUM; i++) { |
| if ((share_reg & mdpm_scen[scen_priority[i]].scenario_reg) == |
| mdpm_scen[scen_priority[i]].scenario_reg) { |
| hit = scen_priority[i]; |
| break; |
| } |
| } |
| |
| if (hit == -1) |
| hit = S_STANDBY; |
| #else |
| for (i = 0; i < SCENARIO_NUM; i++) { |
| |
| if (mdpm_scen[i].scenario_reg == share_reg) { |
| hit = i; |
| break; |
| } |
| } |
| |
| if (hit == -1) |
| pr_notice("[%s] ERROR, unknown scenario [%d]\n", |
| __func__, share_reg); |
| #endif |
| |
| return hit; |
| } |
| |
| static int get_shm_idx(enum tx_power_table tx_dbm, int sec_shift, bool get_dbm) |
| { |
| int idx = 0, mem_idx = 0; |
| |
| idx = sec_shift / 32; |
| |
| if (idx >= DBM_TABLE_SIZE || idx < 0 || tx_dbm >= TX_DBM_NUM |
| || tx_dbm < 0) { |
| pr_notice("[%s] ERROR, exceed index %d %d %d\n", |
| __func__, idx, sec_shift, tx_dbm); |
| WARN_ON_ONCE(1); |
| return mem_idx; |
| } |
| |
| if (get_dbm) { |
| /* get dbm index */ |
| mem_idx = mdpm_tx_pwr[tx_dbm].shm_dbm_idx[idx]; |
| if (mem_idx > DBM_TABLE_END |
| || mem_idx < DBM_TABLE_START) { |
| pr_notice("[%s] ERROR, not in dbm table %d %d %d\n", |
| __func__, idx, tx_dbm, mem_idx); |
| WARN_ON_ONCE(1); |
| } |
| } else { |
| /* get section index */ |
| mem_idx = mdpm_tx_pwr[tx_dbm].shm_sec_idx[idx]; |
| if (!(mem_idx <= SECTION_LEVEL_END |
| && mem_idx >= SECTION_LEVEL_START) |
| && !(mem_idx <= SECTION_LEVEL_2_END |
| && mem_idx >= SECTION_LEVEL_2_START)) { |
| pr_notice("[%s] ERROR, not in section level %d %d %d\n", |
| __func__, idx, tx_dbm, mem_idx); |
| WARN_ON_ONCE(1); |
| } |
| } |
| |
| return mem_idx; |
| } |
| |
| static int mdpm_shm_write(u32 *share_mem, enum share_mem_mapping mem_idx, |
| u32 value, u32 mask, u32 shift) |
| { |
| if (mem_idx < 0 || mem_idx >= SHARE_MEM_SIZE) |
| return -EINVAL; |
| |
| share_mem[mem_idx] = (share_mem[mem_idx] & (~(mask << shift))) |
| | ((value & mask) << shift); |
| |
| return 0; |
| } |
| |
| static int mdpm_shm_read(u32 *share_mem, enum share_mem_mapping mem_idx, |
| u32 *value, u32 mask, u32 shift) |
| { |
| if (mem_idx < 0 || mem_idx >= SHARE_MEM_SIZE) |
| return -EINVAL; |
| |
| *value = (share_mem[mem_idx] >> shift) & mask; |
| |
| return 0; |
| } |
| |
| static u32 check_shm_version(u32 *share_mem) |
| { |
| static u32 mdpm_version_check; |
| |
| if (mdpm_version_check == VERSION_INIT) { |
| mdpm_shm_read(share_mem, M_VERSION_CHECK, |
| &mdpm_version_check, VERSION_CHECK_VALID_MASK, |
| VERSION_CHECK_VALID_SHIFT); |
| } |
| |
| switch (mdpm_version_check) { |
| case VERSION_INIT: |
| if (mt_mdpm_debug) |
| pr_info_ratelimited("mdpm share memory: MD not init\n"); |
| |
| break; |
| case VERSION_INVALID: |
| pr_info("dpm share memory: MD check version ERROR\n"); |
| WARN_ON_ONCE(1); |
| break; |
| case VERSION_VALID: |
| default: |
| break; |
| } |
| |
| return mdpm_version_check; |
| } |
| |
| static u32 get_rfhw(u32 *share_mem) |
| { |
| u32 rfhw_version; |
| static u32 rfhw_check; |
| static bool rfhw_updated; |
| |
| if (rfhw_updated == 1) |
| return rfhw_sel; |
| else if (rfhw_check == RF_HW_INIT) { |
| mdpm_shm_read(share_mem, M_RF_HW, &rfhw_check, |
| RF_HW_VALID_MASK, RF_HW_VALID_SHIFT); |
| } |
| |
| if (rfhw_check == RF_HW_VALID) { |
| mdpm_shm_read(share_mem, M_RF_HW, &rfhw_version, |
| RF_HW_VERSION_MASK, RF_HW_VERSION_SHIFT); |
| if (rfhw_version >= 0 && rfhw_version < RF_HW_NUM) { |
| rfhw_updated = 1; |
| return rfhw_version; |
| } else if (1) |
| pr_notice("wrong rfhw_version %d\n", rfhw_version); |
| } |
| |
| return rfhw_sel; |
| } |
| |
| void init_version_check(u32 *share_mem) |
| { |
| mdpm_shm_write(share_mem, M_VERSION_CHECK, VERSION_INIT, |
| VERSION_CHECK_VALID_MASK, VERSION_CHECK_VALID_SHIFT); |
| mdpm_shm_write(share_mem, M_VERSION_CHECK, AP_MD_MDPM_VERSION, |
| VERSION_CHECK_VERSION_MASK, VERSION_CHECK_VERSION_SHIFT); |
| } |
| |
| unsigned int get_md1_status_reg(void) |
| { |
| #ifdef GET_MD_SCEANRIO_BY_SHARE_MEMORY |
| return 0; |
| #else |
| return vcorefs_get_md_scenario(); |
| #endif |
| } |
| |
| void init_md1_section_level(u32 *share_mem) |
| { |
| u32 mem[SHARE_MEM_SIZE]; |
| int i, j, index, offset; |
| |
| memset(&mem[0], 0, sizeof(u32) * SHARE_MEM_SIZE); |
| |
| for (i = 0; i < DBM_SECTION_NUM; i++) { |
| for (j = 0; j < TX_DBM_NUM; j++) { |
| if (!mdpm_tx_pwr[j].rfhw) |
| continue; |
| if (mdpm_tx_pwr[j].rfhw->section[i] > |
| DBM_SECTION_MASK) { |
| pr_notice("[%s] md1_section_level too large i:%d s:%d !\n", |
| __func__, j, i); |
| WARN_ON_ONCE(1); |
| } |
| |
| index = get_shm_idx(j, section_shift[i], false); |
| offset = section_shift[i] % 32; |
| if (index >= SHARE_MEM_SIZE) { |
| pr_notice("%s out of bounds, index:%d\n", __func__, index); |
| return; |
| } |
| mem[index] |= mdpm_tx_pwr[j].rfhw->section[i] << offset; |
| } |
| } |
| |
| memcpy(&share_mem[SECTION_LEVEL_START], &mem[SECTION_LEVEL_START], |
| sizeof(u32) * (SECTION_LEVEL_END - SECTION_LEVEL_START + 1)); |
| |
| memcpy(&share_mem[SECTION_LEVEL_2_START], &mem[SECTION_LEVEL_2_START], |
| sizeof(u32) * (SECTION_LEVEL_2_END - SECTION_LEVEL_2_START + 1)); |
| |
| pr_info("AP2MD1 section, 2G: 0x%08x%08x(0x%08x%08x), 3G: 0x%08x%08x(0x%08x %08x)\n", |
| mem[M_2G_SECTION_LEVEL], mem[M_2G_SECTION_1_LEVEL], |
| share_mem[M_2G_SECTION_LEVEL], share_mem[M_2G_SECTION_1_LEVEL], |
| mem[M_3G_SECTION_LEVEL], mem[M_3G_SECTION_1_LEVEL], |
| share_mem[M_3G_SECTION_LEVEL], |
| share_mem[M_3G_SECTION_1_LEVEL]); |
| pr_info("4G_CC0:0x%08x%08x(0x%08x%08x),4G_CC1:0x%08x%08x(0x%08x%08x)\n", |
| mem[M_4G_SECTION_LEVEL], mem[M_4G_SECTION_9_LEVEL], |
| share_mem[M_4G_SECTION_LEVEL], share_mem[M_4G_SECTION_9_LEVEL], |
| mem[M_4G_SECTION_LEVEL], mem[M_4G_SECTION_9_LEVEL], |
| share_mem[M_4G_SECTION_LEVEL], |
| share_mem[M_4G_SECTION_9_LEVEL]); |
| pr_info("3GTDD: 0x%08x%08x(0x%08x%08x)\n", |
| mem[M_TDD_SECTION_LEVEL], mem[M_TDD_SECTION_1_LEVEL], |
| share_mem[M_TDD_SECTION_LEVEL], |
| share_mem[M_TDD_SECTION_1_LEVEL]); |
| pr_info("C2K: 0x%08x%08x(0x%08x%08x), addr: 0x%p\n", |
| mem[M_C2K_SECTION_1_LEVEL], mem[M_C2K_SECTION_2_LEVEL], |
| share_mem[M_C2K_SECTION_1_LEVEL], |
| share_mem[M_C2K_SECTION_2_LEVEL], share_mem); |
| pr_info("NR_CC0:0x%08x%08x(0x%08x%08x),NR_CC1:0x%08x%08x(0x%08x%08x)\n", |
| mem[M_NR_SECTION_LEVEL], mem[M_NR_SECTION_1_LEVEL], |
| share_mem[M_NR_SECTION_LEVEL], share_mem[M_NR_SECTION_1_LEVEL], |
| mem[M_NR_SECTION_2_LEVEL], mem[M_NR_SECTION_3_LEVEL], |
| share_mem[M_NR_SECTION_2_LEVEL], |
| share_mem[M_NR_SECTION_3_LEVEL]); |
| pr_info("MMW_TX1:0x%08x%08x(0x%08x%08x),MMW_TX2:0x%08x%08x(0x%08x%08x)\n", |
| mem[M_MMW_SECTION_LEVEL], mem[M_MMW_SECTION_1_LEVEL], |
| share_mem[M_MMW_SECTION_LEVEL], share_mem[M_MMW_SECTION_1_LEVEL], |
| mem[M_MMW_SECTION_2_LEVEL], mem[M_MMW_SECTION_3_LEVEL], |
| share_mem[M_MMW_SECTION_2_LEVEL], |
| share_mem[M_MMW_SECTION_3_LEVEL]); |
| } |
| |
| #ifdef GET_MD_SCEANRIO_BY_SHARE_MEMORY |
| enum md_scenario get_md1_scenario_by_shm(u32 *share_mem) |
| { |
| int scenario = S_STANDBY; |
| u32 scen_status = -1; |
| |
| mdpm_shm_read(share_mem, M_MD_SCENARIO, &scen_status, |
| MDPM_SHARE_MEMORY_MASK, MDPM_SHARE_MEMORY_SHIFT); |
| |
| scenario = get_md1_scenario_internal(scen_status); |
| |
| scenario = (scenario < 0) ? S_STANDBY : scenario; |
| |
| if (mt_mdpm_debug) |
| pr_info("MD1 scenario: %d(%s), scen_status: 0x%x\n", |
| scenario, mdpm_scen[scenario].scenario_name, |
| scen_status); |
| |
| return scenario; |
| } |
| #else |
| enum md_scenario get_md1_scenario(u32 share_reg, |
| enum mdpm_power_type power_type) |
| { |
| int scenario = S_STANDBY; |
| |
| share_reg = share_reg & SHARE_REG_MASK; |
| |
| scenario = get_md1_scenario_internal(share_reg); |
| |
| scenario = (scenario < 0) ? S_STANDBY : scenario; |
| |
| if (mt_mdpm_debug) |
| pr_info("MD1 scenario: %d(%s), reg: 0x%x\n", |
| scenario, mdpm_scen[scenario].scenario_name, |
| share_reg); |
| |
| return scenario; |
| } |
| #endif |
| |
| int get_md1_scenario_power(enum md_scenario scenario, |
| enum mdpm_power_type power_type, struct md_power_status *mdpm_pwr_sta) |
| { |
| int ret; |
| int s_power = 0; |
| |
| switch (power_type) { |
| case MAX_POWER: |
| s_power = mdpm_scen[scenario].scenario_power->max; |
| break; |
| case AVG_POWER: |
| s_power = mdpm_scen[scenario].scenario_power->avg; |
| break; |
| default: |
| pr_notice("%s error power_type=%d\n", __func__, power_type); |
| break; |
| } |
| mdpm_pwr_sta->scenario_id = scenario; |
| ret = snprintf(mdpm_pwr_sta->scenario_name, sizeof(mdpm_pwr_sta->scenario_name), |
| "%s", mdpm_scen[scenario].scenario_name); |
| if (ret < 0 || ret >= sizeof(mdpm_pwr_sta->scenario_name)) |
| pr_notice("%s snprintf failed, ret =%d\n", __func__, ret); |
| mdpm_pwr_sta->scanario_power = s_power; |
| mdpm_pwr_sta->power_type = power_type; |
| |
| return s_power; |
| } |
| |
| static int get_md1_tx_power_by_table(u32 *dbm_mem, u32 *old_dbm_mem, |
| struct tx_power *tx_pwr, enum tx_power_table dbm_type, |
| enum tx_rat_type rat, enum mdpm_power_type power_type, |
| int *sec_shift, struct md_power_status *md_power_s) |
| { |
| |
| int pa_power = 0, rf_power = 0; |
| int section, index, offset, i; |
| bool cmp = true; |
| |
| if (dbm_type >= TX_DBM_NUM || dbm_type < 0 || |
| power_type >= POWER_TYPE_NUM || power_type < 0) { |
| pr_notice("error argument dbm_type=%d power_type=%d\n", |
| dbm_type, power_type); |
| return 0; |
| } |
| |
| if (tx_pwr == NULL) { |
| pr_notice("no data for tx_power\n"); |
| return 0; |
| } |
| |
| for (i = 0; i < DBM_TABLE_SIZE; i++) { |
| if (dbm_mem[tx_pwr->shm_dbm_idx[i]] != |
| old_dbm_mem[tx_pwr->shm_dbm_idx[i]]) { |
| cmp = false; |
| break; |
| } |
| } |
| |
| if (cmp) { |
| if (mt_mdpm_debug == 2) |
| pr_info("%s dBm no TX power, reg: 0x%08x%08x(0x%08x%08x) return 0\n", |
| tx_pwr->dbm_name, |
| dbm_mem[tx_pwr->shm_dbm_idx[0]], |
| dbm_mem[tx_pwr->shm_dbm_idx[1]], |
| old_dbm_mem[tx_pwr->shm_dbm_idx[0]], |
| old_dbm_mem[tx_pwr->shm_dbm_idx[1]]); |
| |
| return 0; |
| } |
| |
| for (section = 0; section < DBM_SECTION_NUM; section++) { |
| index = get_shm_idx(dbm_type, sec_shift[section], true); |
| offset = sec_shift[section] % 32; |
| |
| if (((dbm_mem[index] >> offset) & DBM_SECTION_MASK) != |
| ((old_dbm_mem[index] >> offset) & DBM_SECTION_MASK)) { |
| |
| switch (power_type) { |
| case MAX_POWER: |
| pa_power = tx_pwr->rfhw->pa_power.max[section]; |
| rf_power = tx_pwr->rfhw->rf_power.max[section]; |
| break; |
| case AVG_POWER: |
| pa_power = tx_pwr->rfhw->pa_power.avg[section]; |
| rf_power = tx_pwr->rfhw->rf_power.avg[section]; |
| break; |
| default: |
| pr_notice("%s error power_type=%d\n", |
| __func__, power_type); |
| break; |
| } |
| |
| if (mt_mdpm_debug) |
| pr_info("%s dBm: reg:0x%08x%08x(0x%08x%08x),pa:%d,rf:%d,s:%d\n", |
| tx_pwr->dbm_name, dbm_mem[tx_pwr->shm_dbm_idx[0]], |
| dbm_mem[tx_pwr->shm_dbm_idx[1]], |
| old_dbm_mem[tx_pwr->shm_dbm_idx[0]], |
| old_dbm_mem[tx_pwr->shm_dbm_idx[1]], |
| pa_power, rf_power, section+1); |
| |
| |
| for (i = 0; i < DBM_TABLE_SIZE; i++) { |
| memcpy( |
| &old_dbm_mem[tx_pwr->shm_dbm_idx[i]], |
| &dbm_mem[tx_pwr->shm_dbm_idx[i]], sizeof(u32)); |
| } |
| |
| md_power_s->rat = rat; |
| md_power_s->power_type = power_type; |
| md_power_s->dbm_section = section + 1; |
| md_power_s->pa_power += pa_power; |
| md_power_s->rf_power += rf_power; |
| md_power_s->tx_power += pa_power + rf_power; |
| |
| break; |
| } |
| } |
| |
| return pa_power + rf_power; |
| |
| } |
| |
| static int get_md1_tx_power_by_rat(u32 *dbm_mem, u32 *old_dbm_mem, |
| enum tx_rat_type rat, enum mdpm_power_type power_type, |
| struct md_power_status *md_power_s) |
| { |
| int power; |
| struct tx_power *tx_pwr = NULL; |
| |
| if (rat > RAT_NUM || rat <= 0 || |
| power_type >= POWER_TYPE_NUM || power_type < 0) { |
| pr_notice("error argument rat_type=%d power_type=%d\n", rat, |
| power_type); |
| return 0; |
| } |
| |
| switch (rat) { |
| case RAT_2G: |
| tx_pwr = &mdpm_tx_pwr[TX_2G_DBM]; |
| power = get_md1_tx_power_by_table(dbm_mem, |
| old_dbm_mem, tx_pwr, TX_2G_DBM, rat, power_type, |
| section_shift, md_power_s); |
| break; |
| case RAT_3G: |
| tx_pwr = &mdpm_tx_pwr[TX_3G_DBM]; |
| power = get_md1_tx_power_by_table(dbm_mem, |
| old_dbm_mem, tx_pwr, TX_3G_DBM, rat, power_type, |
| section_shift, md_power_s); |
| break; |
| case RAT_4G: |
| tx_pwr = &mdpm_tx_pwr[TX_4G_CC0_DBM]; |
| power = get_md1_tx_power_by_table(dbm_mem, |
| old_dbm_mem, tx_pwr, TX_4G_CC0_DBM, rat, power_type, |
| section_shift, md_power_s); |
| tx_pwr = &mdpm_tx_pwr[TX_4G_CC1_DBM]; |
| power = power + get_md1_tx_power_by_table(dbm_mem, |
| old_dbm_mem, tx_pwr, TX_4G_CC1_DBM, rat, power_type, |
| section_shift, md_power_s); |
| break; |
| case RAT_5G: |
| tx_pwr = &mdpm_tx_pwr[TX_NR_CC0_DBM]; |
| power = get_md1_tx_power_by_table(dbm_mem, |
| old_dbm_mem, tx_pwr, TX_NR_CC0_DBM, rat, power_type, |
| section_shift, md_power_s); |
| tx_pwr = &mdpm_tx_pwr[TX_NR_CC1_DBM]; |
| power = power + get_md1_tx_power_by_table(dbm_mem, |
| old_dbm_mem, tx_pwr, TX_NR_CC1_DBM, rat, power_type, |
| section_shift, md_power_s); |
| break; |
| case RAT_MMW: |
| tx_pwr = &mdpm_tx_pwr[TX_MMW_TX2_DBM]; |
| power = get_md1_tx_power_by_table(dbm_mem, |
| old_dbm_mem, tx_pwr, TX_MMW_TX2_DBM, rat, power_type, |
| section_shift, md_power_s); |
| if (!power) { |
| tx_pwr = &mdpm_tx_pwr[TX_MMW_TX1_DBM]; |
| power = get_md1_tx_power_by_table(dbm_mem, |
| old_dbm_mem, tx_pwr, TX_MMW_TX1_DBM, rat, power_type, |
| section_shift, md_power_s); |
| } |
| break; |
| case RAT_C2K: |
| tx_pwr = &mdpm_tx_pwr[TX_C2K_DBM]; |
| power = get_md1_tx_power_by_table(dbm_mem, |
| old_dbm_mem, tx_pwr, TX_C2K_DBM, rat, power_type, |
| section_shift, md_power_s); |
| break; |
| case RAT_3GTDD: |
| tx_pwr = &mdpm_tx_pwr[TX_3GTDD_DBM]; |
| power = get_md1_tx_power_by_table(dbm_mem, |
| old_dbm_mem, tx_pwr, TX_3GTDD_DBM, rat, power_type, |
| section_shift, md_power_s); |
| break; |
| default: |
| pr_notice("error argument rat_type=%d power_type=%d\n", |
| rat, power_type); |
| break; |
| } |
| |
| return power; |
| } |
| |
| int get_md1_tx_power(enum md_scenario scenario, u32 *share_mem, |
| enum mdpm_power_type power_type, |
| struct md_power_status *mdpm_pwr_sta) |
| { |
| int i, rf_ret, tx_power, tx_power_max, usedBytes = 0; |
| enum tx_rat_type rat; |
| struct md_power_status mdpm_power_s_tmp; |
| char log_buffer[128]; |
| |
| if (share_mem == NULL) { |
| if (mt_mdpm_debug) |
| pr_info("MD1 share_mem is NULL\n"); |
| |
| return 0; |
| } |
| |
| if (check_shm_version(share_mem) == VERSION_VALID) |
| rf_ret = get_rfhw(share_mem); |
| else |
| return 0; |
| |
| if (mt_mdpm_debug == 2) |
| for (i = 0; i < SHARE_MEM_SIZE; i++) { |
| usedBytes += sprintf(log_buffer + usedBytes, "0x%x ", |
| share_mem[i]); |
| |
| if ((i + 1) % 10 == 0 || (i + 1) == SHARE_MEM_SIZE) { |
| usedBytes = 0; |
| pr_info("%s\n", log_buffer); |
| } |
| } |
| |
| memset((void *)&mdpm_power_s_tmp, 0, sizeof(struct md_power_status)); |
| tx_power_max = 0; |
| |
| for (i = 0; i < MAX_DBM_FUNC_NUM; i++) { |
| rat = mdpm_scen[scenario].tx_power_rat[i]; |
| if (rat == 0) |
| break; |
| |
| tx_power = get_md1_tx_power_by_rat(share_mem, |
| cnted_share_mem, rat, power_type, &mdpm_power_s_tmp); |
| |
| if (mdpm_scen[scenario].tx_power_rat_sum == true) { |
| /* select sum RAT Tx power */ |
| tx_power_max += tx_power; |
| mdpm_pwr_sta->rat |= mdpm_power_s_tmp.rat; |
| mdpm_pwr_sta->power_type = mdpm_power_s_tmp.power_type; |
| |
| mdpm_pwr_sta->dbm_section = |
| mdpm_power_s_tmp.dbm_section; |
| mdpm_pwr_sta->pa_power += mdpm_power_s_tmp.pa_power; |
| mdpm_pwr_sta->rf_power += mdpm_power_s_tmp.rf_power; |
| mdpm_pwr_sta->tx_power += mdpm_power_s_tmp.tx_power; |
| mdpm_pwr_sta->total_power += |
| mdpm_pwr_sta->scanario_power + |
| mdpm_pwr_sta->tx_power; |
| } else { |
| /* select largest RAT Tx power */ |
| if (tx_power > tx_power_max) { |
| tx_power_max = tx_power; |
| mdpm_pwr_sta->rat = mdpm_power_s_tmp.rat; |
| mdpm_pwr_sta->power_type = |
| mdpm_power_s_tmp.power_type; |
| mdpm_pwr_sta->dbm_section = |
| mdpm_power_s_tmp.dbm_section; |
| mdpm_pwr_sta->pa_power = |
| mdpm_power_s_tmp.pa_power; |
| mdpm_pwr_sta->rf_power = |
| mdpm_power_s_tmp.rf_power; |
| mdpm_pwr_sta->tx_power = |
| mdpm_power_s_tmp.tx_power; |
| mdpm_pwr_sta->total_power = |
| mdpm_pwr_sta->scanario_power + |
| mdpm_pwr_sta->tx_power; |
| } |
| } |
| } |
| |
| return tx_power_max; |
| } |
| |
| static int mdpm_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct mdpm_data *mdpm_data; |
| |
| mdpm_data = (struct mdpm_data *) of_device_get_match_data(dev); |
| |
| if (!mdpm_data) { |
| dev_notice(dev, "Error: Failed to get mdpm platform data\n"); |
| return -ENODATA; |
| } |
| |
| mdpm_data->dev = &pdev->dev; |
| |
| platform_set_drvdata(pdev, mdpm_data); |
| |
| mdpm_tx_pwr = mdpm_data->tx_power_t; |
| mdpm_scen = mdpm_data->scenario_power_t; |
| scen_priority = mdpm_data->prority_t; |
| |
| mt_mdpm_create_procfs(); |
| |
| return 0; |
| } |
| |
| enum mdpm_platform { |
| MT6873_MDPM_DATA, |
| MT6893_MDPM_DATA, |
| MT6983_MDPM_DATA, |
| MT6879_MDPM_DATA, |
| MT6895_MDPM_DATA, |
| MT6886_MDPM_DATA, |
| MT6985_MDPM_DATA |
| }; |
| |
| static struct mdpm_data mt6873_mdpm_data = { |
| .platform = MT6873_MDPM_DATA, |
| .scenario_power_t = mt6873_mdpm_scen, |
| .tx_power_t = mt6873_mdpm_tx_pwr, |
| .prority_t = (void *)&mt6873_scen_priority |
| }; |
| |
| static struct mdpm_data mt6893_mdpm_data = { |
| .platform = MT6893_MDPM_DATA, |
| .scenario_power_t = mt6893_mdpm_scen, |
| .tx_power_t = mt6893_mdpm_tx_pwr, |
| .prority_t = (void *)&mt6893_scen_priority |
| }; |
| |
| static struct mdpm_data mt6983_mdpm_data = { |
| .platform = MT6983_MDPM_DATA, |
| .scenario_power_t = mt6983_mdpm_scen, |
| .tx_power_t = mt6983_mdpm_tx_pwr, |
| .prority_t = (void *)&mt6983_scen_priority |
| }; |
| |
| static struct mdpm_data mt6879_mdpm_data = { |
| .platform = MT6879_MDPM_DATA, |
| .scenario_power_t = mt6879_mdpm_scen, |
| .tx_power_t = mt6879_mdpm_tx_pwr, |
| .prority_t = (void *)&mt6879_scen_priority |
| }; |
| |
| static struct mdpm_data mt6895_mdpm_data = { |
| .platform = MT6895_MDPM_DATA, |
| .scenario_power_t = mt6895_mdpm_scen, |
| .tx_power_t = mt6895_mdpm_tx_pwr, |
| .prority_t = (void *)&mt6895_scen_priority |
| }; |
| |
| static struct mdpm_data mt6886_mdpm_data = { |
| .platform = MT6886_MDPM_DATA, |
| .scenario_power_t = mt6886_mdpm_scen, |
| .tx_power_t = mt6886_mdpm_tx_pwr, |
| .prority_t = (void *)&mt6886_scen_priority |
| }; |
| |
| static struct mdpm_data mt6985_mdpm_data = { |
| .platform = MT6985_MDPM_DATA, |
| .scenario_power_t = mt6985_mdpm_scen, |
| .tx_power_t = mt6985_mdpm_tx_pwr, |
| .prority_t = (void *)&mt6985_scen_priority |
| }; |
| |
| static const struct of_device_id mdpm_of_match[] = { |
| { |
| .compatible = "mediatek,mt6873-mdpm", |
| .data = (void *)&mt6873_mdpm_data, |
| }, |
| { |
| .compatible = "mediatek,mt6893-mdpm", |
| .data = (void *)&mt6893_mdpm_data, |
| }, |
| { |
| .compatible = "mediatek,mt6983-mdpm", |
| .data = (void *)&mt6983_mdpm_data, |
| }, |
| { |
| .compatible = "mediatek,mt6879-mdpm", |
| .data = (void *)&mt6879_mdpm_data, |
| }, |
| { |
| .compatible = "mediatek,mt6895-mdpm", |
| .data = (void *)&mt6895_mdpm_data, |
| }, |
| { |
| .compatible = "mediatek,mt6886-mdpm", |
| .data = (void *)&mt6886_mdpm_data, |
| }, |
| { |
| .compatible = "mediatek,mt6985-mdpm", |
| .data = (void *)&mt6985_mdpm_data, |
| }, |
| { |
| }, |
| }; |
| |
| MODULE_DEVICE_TABLE(of, mdpm_of_match); |
| |
| static struct platform_driver mdpm_driver = { |
| .driver = { |
| .name = "mtk-modem_power_meter", |
| .of_match_table = mdpm_of_match, |
| }, |
| .probe = mdpm_probe, |
| }; |
| |
| module_platform_driver(mdpm_driver); |
| |
| MODULE_AUTHOR("Samuel Hsieh"); |
| MODULE_DESCRIPTION("MTK modem power meter driver"); |
| MODULE_LICENSE("GPL"); |