| /* |
| * common function for clock framework source file |
| * |
| * Copyright (C) 2012 Marvell |
| * Chao Xie <xiechao.mail@gmail.com> |
| * Zhoujie Wu <zjwu@marvell.com> |
| * Lu Cao <lucao@marvell.com> |
| * |
| * This file is licensed under the terms of the GNU General Public |
| * License version 2. This program is licensed "as is" without any |
| * warranty of any kind, whether express or implied. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/cpufreq.h> |
| #include <linux/devfreq.h> |
| #include <linux/clk/mmpcpdvc.h> |
| |
| #include "clk.h" |
| |
| enum comp { |
| _CORE, |
| _DDR, |
| _AXI, |
| BOOT_DVFS_MAX, |
| }; |
| |
| static unsigned long minfreq[BOOT_DVFS_MAX] = { 0, 0, 0 }; |
| static unsigned long bootfreq[BOOT_DVFS_MAX]; |
| static struct clk *clk[BOOT_DVFS_MAX]; |
| static char *clk_name[BOOT_DVFS_MAX] = {"cpu", "ddr", "axi"}; |
| |
| static struct pm_qos_request sdh_core_qos_max, sdh_ddr_qos_max; |
| static struct pm_qos_request sdh_core_qos_min; |
| |
| static int __init sdh_tuning_init(void) |
| { |
| #if defined(CONFIG_CPU_FREQ) && defined(CONFIG_PM_DEVFREQ) |
| struct cpufreq_frequency_table *cpufreq_table = |
| cpufreq_frequency_get_table(0); |
| struct devfreq_frequency_table *ddrfreq_table = |
| devfreq_frequency_get_table(DEVFREQ_DDR); |
| |
| if (cpufreq_table) |
| minfreq[_CORE] = cpufreq_table[0].frequency; |
| else |
| pr_err("%s cpufreq_table get failed, use 0 to set!\n", __func__); |
| |
| if (ddrfreq_table) |
| minfreq[_DDR] = ddrfreq_table[0].frequency; |
| else |
| pr_err("%s ddrfreq_table get failed, use 0 to set!\n", __func__); |
| #else |
| pr_info("%s CONFIG_CPU_FREQ & CONFIG_PM_DEVFREQ not defined!\n", __func__); |
| minfreq[_CORE] = 0; |
| minfreq[_DDR] = 0; |
| #endif |
| |
| sdh_core_qos_max.name = "sdh_tuning_core_max"; |
| pm_qos_add_request(&sdh_core_qos_max, |
| PM_QOS_CPUFREQ_MAX, PM_QOS_DEFAULT_VALUE); |
| |
| sdh_core_qos_min.name = "sdh_tuning_core_min"; |
| pm_qos_add_request(&sdh_core_qos_min, |
| PM_QOS_CPUFREQ_MIN, PM_QOS_DEFAULT_VALUE); |
| |
| sdh_ddr_qos_max.name = "sdh_tuning_ddr_max"; |
| pm_qos_add_request(&sdh_ddr_qos_max, |
| PM_QOS_DDR_DEVFREQ_MAX, PM_QOS_DEFAULT_VALUE); |
| |
| return 0; |
| } |
| arch_initcall(sdh_tuning_init); |
| |
| int sdh_tunning_savefreq(void) |
| { |
| int i; |
| |
| for (i = 0; i < BOOT_DVFS_MAX; i++) { |
| if (unlikely(!clk[i])) { |
| clk[i] = clk_get(NULL, clk_name[i]); |
| if (!clk[i]) { |
| pr_err("failed to get clk %s\n", clk_name[i]); |
| return -1; |
| } |
| } |
| |
| if (!bootfreq[i]) |
| bootfreq[i] = clk_get_rate(clk[i]); |
| |
| } |
| #if defined(CONFIG_ARM64) && defined(CONFIG_ASR_DVFS) |
| return 0; |
| #else |
| clk_set_rate(clk[_AXI], minfreq[_AXI]*1000); |
| pm_qos_update_request(&sdh_core_qos_max, minfreq[_CORE]); |
| pm_qos_update_request(&sdh_ddr_qos_max, minfreq[_DDR]); |
| #endif |
| return 0; |
| } |
| |
| #if defined(CONFIG_ARM64) && defined(CONFIG_ASR_DVFS) |
| int sdh_tunning_updatefreq(unsigned int dvfs_level) |
| { |
| long freq; |
| |
| /* request PM_QOS_CPUFREQ_MAX */ |
| freq = get_dvc_freq(0, dvfs_level); |
| if (freq >= 0) { |
| pm_qos_update_request(&sdh_core_qos_max, (s32)(freq/1000)); |
| pm_qos_update_request(&sdh_core_qos_min, (s32)(freq/1000)); |
| } else { |
| pm_qos_update_request(&sdh_core_qos_max, 0); |
| pr_err("fail to get CORE freq for dvfs level %d\n", dvfs_level); |
| } |
| /* request PM_QOS_DDR_DEVFREQ_MAX */ |
| freq = get_dvc_freq(2, dvfs_level); |
| if (freq >= 0) { |
| pm_qos_update_request(&sdh_ddr_qos_max, (s32)(freq/1000)); |
| } else { |
| pm_qos_update_request(&sdh_ddr_qos_max, 0); |
| pr_err("fail to get DDR freq for dvfs level %d\n", dvfs_level); |
| } |
| |
| return 0; |
| } |
| #else |
| int sdh_tunning_updatefreq(unsigned int dvfs_level) |
| { |
| return 0; |
| } |
| #endif |
| |
| int sdh_tunning_restorefreq(void) |
| { |
| int i; |
| |
| pm_qos_update_request(&sdh_core_qos_max, PM_QOS_DEFAULT_VALUE); |
| pm_qos_update_request(&sdh_ddr_qos_max, PM_QOS_DEFAULT_VALUE); |
| #if defined(CONFIG_ARM64) && defined(CONFIG_ASR_DVFS) |
| pm_qos_update_request(&sdh_core_qos_min, PM_QOS_DEFAULT_VALUE); |
| #endif |
| for (i = 0; i < BOOT_DVFS_MAX; i++) { |
| if (!clk[i]) |
| continue; |
| if (i != _CORE) |
| clk_set_rate(clk[i], bootfreq[i]); |
| } |
| return 0; |
| } |
| |
| static unsigned int platvl_min, platvl_max; |
| |
| void plat_set_vl_min(u32 vl_num) |
| { |
| platvl_min = vl_num; |
| } |
| |
| unsigned int plat_get_vl_min(void) |
| { |
| return platvl_min; |
| } |
| void plat_set_vl_max(u32 vl_num) |
| { |
| platvl_max = vl_num; |
| } |
| |
| unsigned int plat_get_vl_max(void) |
| { |
| return platvl_max; |
| } |
| |
| static struct cpmsa_dvc_info cpmsadvcinfo; |
| |
| /* |
| * This interface will be used by different platform to fill CP DVC info |
| */ |
| int fillcpdvcinfo(struct cpmsa_dvc_info *dvc_info) |
| { |
| if (!dvc_info) |
| return -EINVAL; |
| |
| memcpy(&cpmsadvcinfo, dvc_info, sizeof(struct cpmsa_dvc_info)); |
| return 0; |
| } |
| |
| /* |
| * This interface will be used by telephony to get CP DVC info, and |
| * they will use ACIPC to pass the info to CP |
| */ |
| int getcpdvcinfo(struct cpmsa_dvc_info *dvc_info) |
| { |
| if (!dvc_info) |
| return -EINVAL; |
| |
| memcpy(dvc_info, &cpmsadvcinfo, sizeof(struct cpmsa_dvc_info)); |
| return 0; |
| } |
| EXPORT_SYMBOL(getcpdvcinfo); |
| |
| static struct ddr_dfc_info ddrdfc; |
| |
| /* |
| * This interface will be used by different platform to fill CP ddr dfc info |
| */ |
| int fillddrdfcinfo(struct ddr_dfc_info *dfc_info) |
| { |
| if (!dfc_info) |
| return -EINVAL; |
| |
| memcpy(&ddrdfc, dfc_info, sizeof(struct ddr_dfc_info)); |
| return 0; |
| } |
| |
| /* |
| * This interface will be used by telephony to get CP ddr dfc info, and |
| * they will use ACIPC to pass the info to CP |
| */ |
| int getddrdfcinfo(struct ddr_dfc_info *dfc_info) |
| { |
| if (!dfc_info) |
| return -EINVAL; |
| |
| memcpy(dfc_info, &ddrdfc, sizeof(struct ddr_dfc_info)); |
| return 0; |
| } |
| EXPORT_SYMBOL(getddrdfcinfo); |