/* * Copyright (c) 2019 Actions Semiconductor Co., Ltd * * SPDX-License-Identifier: Apache-2.0 */ /** * @file dynamic voltage and frequency scaling interface */ #include #include #include #include #include #include LOG_MODULE_REGISTER(dvfs0, CONFIG_LOG_DEFAULT_LEVEL); #define CONFIG_DVFS_FADE_STEP #ifdef CONFIG_ACTS_DVFS_DYNAMIC_LEVEL struct dvfs_manager { struct k_sem lock; uint8_t cur_dvfs_idx; uint8_t default_dvfs_idx; uint8_t dvfs_level_cnt; uint8_t asrc_limit_clk_mhz; uint8_t spdif_limit_clk_mhz; struct dvfs_level *dvfs_level_tbl; }; struct dvfs_level_max { uint16_t cpu_freq; uint16_t dsp_freq; uint16_t gpu_freq; uint16_t de_freq; uint16_t jpeg_freq; uint16_t vdd_volt; }; static sys_dlist_t __dvfs_notifier_data dvfs_notify_list = SYS_DLIST_STATIC_INIT(&dvfs_notify_list); /* NOTE: DON'T modify max_soc_dvfs_table except actions ic designers */ #define CPU_FREQ_MAX 128 #define VDD_VOLT_MAX 1200 /* vd12 and vdd should satisfy the need for voltage difference */ #define DIFF_VD12_VDD_VOLT_MV 50 static const struct dvfs_level_max max_soc_dvfs_table1[] = { /* cpu_freq, dsp_freq, gpu_freq, de_freq, jpeg_freq, vodd_volt */ {16, 16, 16, 16, 16, 900}, {70, 70, 70, 70, 70, 950}, {94, 94, 94, 94, 140, 1000}, {140, 140, 140, 140, 187, 1100}, {187, 187, 187, 187, 280, 1200}, }; static const struct dvfs_level_max max_soc_dvfs_table0[] = { /* cpu_freq, dsp_freq, gpu_freq, de_freq, jpeg_freq, vodd_volt */ {64, 64, 86, 86, 128, 1000}, {86, 128, 128, 128, 171, 1100}, {128, 171, 171, 171, 256, 1200}, }; static uint16_t dvfs_get_optimal_volt(uint16_t cpu_freq, uint16_t dsp_freq, uint16_t gpu_freq, uint16_t de_freq, uint16_t jpeg_freq, const struct dvfs_level_max *max_soc_dvfs_table, int level_cnt) { uint16_t volt; int i; volt = max_soc_dvfs_table[level_cnt - 1].vdd_volt; for (i = 0; i < level_cnt; i++) { if ((cpu_freq <= max_soc_dvfs_table[i].cpu_freq) && (dsp_freq <= max_soc_dvfs_table[i].dsp_freq) && (gpu_freq <= max_soc_dvfs_table[i].gpu_freq) && (de_freq <= max_soc_dvfs_table[i].de_freq) && (jpeg_freq <= max_soc_dvfs_table[i].jpeg_freq)) { volt = max_soc_dvfs_table[i].vdd_volt; break; } } return volt; } static struct dvfs_level default_soc_dvfs_table1[] = { /* level enable_cnt cpu_freq, dsp_freq, gpu_freq, de_freq, jpeg_freq, vodd_volt */ {DVFS_LEVEL_S2, 0, 70, 70, 70, 70, 70, 0}, {DVFS_LEVEL_NORMAL, 0, 94, 94, 94, 94, 140, 0}, {DVFS_LEVEL_PERFORMANCE, 0, 94, 112, 94, 94, 140, 0}, {DVFS_LEVEL_MID_PERFORMANCE, 0, 140, 112, 140, 140, 187, 0}, {DVFS_LEVEL_HIGH_PERFORMANCE, 0, 187, 187, 187, 187, 280, 0}, }; static struct dvfs_level default_soc_dvfs_table0[] = { /* level enable_cnt cpu_freq, dsp_freq, gpu_freq, de_freq, jpeg_freq, vodd_volt */ {DVFS_LEVEL_S2, 0, 64, 64, 86, 86, 128, 0}, {DVFS_LEVEL_NORMAL, 0, 86, 128, 128, 128, 171, 0}, {DVFS_LEVEL_PERFORMANCE, 0, 86, 128, 128, 128, 171, 0}, {DVFS_LEVEL_MID_PERFORMANCE, 0, 96, 128, 128, 128, 171, 0}, {DVFS_LEVEL_HIGH_PERFORMANCE, 0, 128, 171, 171, 171, 256, 0}, }; static struct dvfs_manager g_dvfs; #if 0 static int level_id_to_vdd(int level_id) { int i; for (i = 0; i < g_dvfs.dvfs_level_cnt; i++) if (g_dvfs.dvfs_level_tbl[i].level_id == level_id) { return g_dvfs.dvfs_level_tbl[i].vdd_volt; } return -1; } #endif static int level_id_to_tbl_idx(int level_id) { int i; for (i = 0; i < g_dvfs.dvfs_level_cnt; i++) { if (g_dvfs.dvfs_level_tbl[i].level_id == level_id) { return i; } } return -1; } static int dvfs_get_max_idx(void) { int i; for (i = (g_dvfs.dvfs_level_cnt - 1); i >= 0; i--) { if (g_dvfs.dvfs_level_tbl[i].enable_cnt > 0) { return i; } } return -1; } static void dvfs_dump_tbl(void) { const struct dvfs_level *dvfs_level = &g_dvfs.dvfs_level_tbl[0]; int i; printk("idx level_id dsp cpu gpu de jpeg vdd enable_cnt\n"); for (i = 0; i < g_dvfs.dvfs_level_cnt; i++, dvfs_level++) { printk("%-6d%-11d%-6d%-6d%-6d%-6d%-6d%-6d%-6d\n", i, dvfs_level->level_id, dvfs_level->dsp_freq, dvfs_level->cpu_freq, dvfs_level->gpu_freq, dvfs_level->de_freq, dvfs_level->jpeg_freq, dvfs_level->vdd_volt, dvfs_level->enable_cnt); } } __dvfs_notifier_func static void dvfs_changed_notify(int state, uint8_t old_level_index, uint8_t new_level_index) { struct dvfs_notifier *obj, *next; struct dvfs_freqs dvfs_freqs_info = {0}; dvfs_freqs_info.state = state; dvfs_freqs_info.old_level = old_level_index; dvfs_freqs_info.new_level = new_level_index; SYS_DLIST_FOR_EACH_CONTAINER_SAFE(&dvfs_notify_list, obj, next, node) { LOG_DBG("dvfs notify state:%d func:%p\n", state, obj->dvfs_notify_func_t); if (obj->dvfs_notify_func_t) obj->dvfs_notify_func_t(obj->user_data, &dvfs_freqs_info); } } static void dvfs_sync(void) { struct dvfs_level *dvfs_level, *old_dvfs_level; int old_idx, new_idx; uint32_t old_dsp_freq, old_volt, volt; uint8_t __old_idx, __new_idx, cur_idx; old_idx = g_dvfs.cur_dvfs_idx; /* get current max dvfs level */ new_idx = dvfs_get_max_idx(); if (new_idx == old_idx) { /* same level, no need sync */ LOG_INF("max idx %d\n", new_idx); return; } /* if all dvfs levels are not enabled to use the default level */ if (-1 == new_idx) { LOG_INF("dvfs use default level:%d", g_dvfs.default_dvfs_idx); new_idx = g_dvfs.default_dvfs_idx; } cur_idx = old_idx; while (cur_idx != new_idx) { __old_idx = cur_idx; #ifdef CONFIG_DVFS_FADE_STEP if (cur_idx > new_idx) cur_idx--; else cur_idx++; #else cur_idx = new_idx; #endif __new_idx = cur_idx; dvfs_level = &g_dvfs.dvfs_level_tbl[__new_idx]; old_dvfs_level = &g_dvfs.dvfs_level_tbl[__old_idx]; old_volt = soc_pmu_get_vdd_voltage(); old_dsp_freq = soc_freq_get_dsp_freq(); LOG_INF("level_id [%d] -> [%d]", old_dvfs_level->level_id, dvfs_level->level_id); /* send notify before clock setting */ dvfs_changed_notify(DVFS_EVENT_PRE_CHANGE, __old_idx, __new_idx); /* set vdd voltage before clock setting if new vdd is up */ if (dvfs_level->vdd_volt > old_volt) { /*check dcdc or ldo mode, if ldo mode ,need add 100mv, dcdc mode add 50mv */ if((sys_read32(VOUT_CTL0) & BIT(17))) { soc_pmu_set_vd12_voltage(dvfs_level->vdd_volt + DIFF_VD12_VDD_VOLT_MV); } else { soc_pmu_set_vd12_voltage(dvfs_level->vdd_volt + DIFF_VD12_VDD_VOLT_MV * 2); } volt = old_volt; while(volt < dvfs_level->vdd_volt) { volt += 50 ; // step 0.05v, for psram/ soc_pmu_set_vdd_voltage(volt); } } printk("new dsp freq %d, cpu freq %d, gpu freq %d, de freq %d, jpeg freq %d, vdd volt %d\n", dvfs_level->dsp_freq, dvfs_level->cpu_freq, dvfs_level->gpu_freq, dvfs_level->de_freq, dvfs_level->jpeg_freq, dvfs_level->vdd_volt); /* adjust core/dsp/cpu clock */ soc_freq_set_cpu_clk(dvfs_level->dsp_freq, dvfs_level->cpu_freq); /* adjust gpu/de/jpeg clock. * freq=0 will be ignored for the case of not used condition, * such as lark not use jpeg, so jpeg_mhz can be set 0. */ soc_freq_set_gpu_clk(dvfs_level->gpu_freq, dvfs_level->de_freq, dvfs_level->jpeg_freq); /* set vdd voltage after clock setting if new vdd is down */ if (dvfs_level->vdd_volt < old_volt) { soc_pmu_set_vdd_voltage(dvfs_level->vdd_volt); /*check dcdc or ldo mode, if ldo mode ,need add 100mv, dcdc mode add 50mv */ if((sys_read32(VOUT_CTL0) & BIT(17))) { soc_pmu_set_vd12_voltage(dvfs_level->vdd_volt + DIFF_VD12_VDD_VOLT_MV); } else { soc_pmu_set_vd12_voltage(dvfs_level->vdd_volt + DIFF_VD12_VDD_VOLT_MV * 2); } } /* send notify after clock setting */ dvfs_changed_notify(DVFS_EVENT_POST_CHANGE, __old_idx, __new_idx); } g_dvfs.cur_dvfs_idx = new_idx; } static int dvfs_update_freq(int level_id, bool is_set, const char *user_info) { struct dvfs_level *dvfs_level; int tbl_idx; LOG_INF("level %d, is_set %d %s", level_id, is_set, user_info); tbl_idx = level_id_to_tbl_idx(level_id); if (tbl_idx < 0) { LOG_ERR("%s: invalid level id %d\n", __func__, level_id); return -EINVAL; } dvfs_level = &g_dvfs.dvfs_level_tbl[tbl_idx]; k_sem_take(&g_dvfs.lock, K_FOREVER); if (is_set) { if(dvfs_level->enable_cnt < 255){ dvfs_level->enable_cnt++; }else{ LOG_WRN("max dvfs level count"); } } else { if (dvfs_level->enable_cnt > 0) { dvfs_level->enable_cnt--; }else{ LOG_WRN("min dvfs level count"); } } dvfs_sync(); k_sem_give(&g_dvfs.lock); return 0; } int dvfs_force_set_level(int level_id, const char *user_info) { return dvfs_update_freq(level_id, 1, user_info); } int dvfs_force_unset_level(int level_id, const char *user_info) { return dvfs_update_freq(level_id, 0, user_info); } static bool dvfs_lock_flag = false; int dvfs_lock(void) { dvfs_lock_flag = true; return 0; } int dvfs_unlock(void) { dvfs_lock_flag = false; return 0; } int dvfs_set_level(int level_id, const char *user_info) { if (!dvfs_lock_flag) { return dvfs_update_freq(level_id, 1, user_info); } else { printk("dvfs locked \n"); return 0; } } int dvfs_unset_level(int level_id, const char *user_info) { if (!dvfs_lock_flag) { return dvfs_update_freq(level_id, 0, user_info); } else { printk("dvfs locked \n"); return 0; } } int dvfs_get_current_level(void) { int idx; if (!g_dvfs.dvfs_level_tbl) return -1; idx = g_dvfs.cur_dvfs_idx; if (idx < 0) { idx = 0; } return g_dvfs.dvfs_level_tbl[idx].level_id; } int dvfs_set_freq_table(struct dvfs_level *dvfs_level_tbl, int level_cnt) { int i; //uint32_t vdd; if ((!dvfs_level_tbl) || (level_cnt <= 0)) return -EINVAL; if (soc_dvfs_opt()) { for (i = 0; i < level_cnt; i++) { dvfs_level_tbl[i].vdd_volt = dvfs_get_optimal_volt(dvfs_level_tbl[i].cpu_freq, dvfs_level_tbl[i].dsp_freq, dvfs_level_tbl[i].gpu_freq, dvfs_level_tbl[i].de_freq, dvfs_level_tbl[i].jpeg_freq, max_soc_dvfs_table1, ARRAY_SIZE(max_soc_dvfs_table1)); } } else { for (i = 0; i < level_cnt; i++) { dvfs_level_tbl[i].vdd_volt = dvfs_get_optimal_volt(dvfs_level_tbl[i].cpu_freq, dvfs_level_tbl[i].dsp_freq, dvfs_level_tbl[i].gpu_freq, dvfs_level_tbl[i].de_freq, dvfs_level_tbl[i].jpeg_freq, max_soc_dvfs_table0, ARRAY_SIZE(max_soc_dvfs_table0)); } } g_dvfs.dvfs_level_cnt = level_cnt; g_dvfs.dvfs_level_tbl = dvfs_level_tbl; /* current dvfs id set to DVFS_LEVEL_HIGH_PERFORMANCE - 1 */ /* make sure we can init DVFS_LEVEL_HIGH_PERFORMANCE first time */ #if 0 /* default level/vdd */ vdd = soc_pmu_get_vdd_voltage(); /* search for current vdd level */ for (i = 0; i < g_dvfs.dvfs_level_cnt; i++) { if (g_dvfs.dvfs_level_tbl[i].vdd_volt >= vdd) { break; } } if (i != g_dvfs.dvfs_level_cnt) g_dvfs.cur_dvfs_idx = i; else g_dvfs.cur_dvfs_idx = 0; #endif g_dvfs.cur_dvfs_idx = level_id_to_tbl_idx(DVFS_LEVEL_MID_PERFORMANCE); g_dvfs.default_dvfs_idx = level_id_to_tbl_idx(DVFS_LEVEL_S2); printk("current dvfs level_id:%d default_dvfs_idx %d \n", g_dvfs.cur_dvfs_idx, g_dvfs.default_dvfs_idx); dvfs_dump_tbl(); return 0; } struct dvfs_level *dvfs_get_info_by_level_id(int level_id) { return &g_dvfs.dvfs_level_tbl[level_id]; } int dvfs_register_notifier(struct dvfs_notifier *notifier) { struct dvfs_notifier *obj; if (!notifier) return -EINVAL; SYS_DLIST_FOR_EACH_CONTAINER(&dvfs_notify_list, obj, node) { if (obj == notifier) { LOG_ERR("dvfs notifier:%p has already registered", notifier); return -EEXIST; } } sys_dlist_append(&dvfs_notify_list, ¬ifier->node); LOG_DBG("dvfs register notifier:%p func:%p\n", notifier, notifier->dvfs_notify_func_t); return 0; } int dvfs_unregister_notifier(struct dvfs_notifier *notifier) { if (!notifier) return -EINVAL; sys_dlist_remove(¬ifier->node); return 0; } #endif /* CONFIG_ACTS_DVFS_DYNAMIC_LEVEL */ static int dvfs_init(const struct device *arg) { ARG_UNUSED(arg); LOG_INF("default dsp freq:%dHz cpu freq:%dHz vdd:%dmV", soc_freq_get_dsp_freq(), soc_freq_get_cpu_freq(), soc_pmu_get_vdd_voltage()); #ifdef CONFIG_ACTS_DVFS_DYNAMIC_LEVEL if (soc_dvfs_opt()) { dvfs_set_freq_table(default_soc_dvfs_table1, ARRAY_SIZE(default_soc_dvfs_table1)); } else { dvfs_set_freq_table(default_soc_dvfs_table0, ARRAY_SIZE(default_soc_dvfs_table0)); } k_sem_init(&g_dvfs.lock, 1, 1); dvfs_set_level(DVFS_LEVEL_HIGH_PERFORMANCE, "init"); #endif return 0; } SYS_INIT(dvfs_init, PRE_KERNEL_1, 20);