#include #include #include #include #include #include #include #include #include #if defined(CONFIG_SOC_S5E9925_EVT0) || defined(CONFIG_SOC_S5E8825) #include #else #include #endif /* Result during profile time */ struct profile_result { struct freq_cstate_result fc_result; s32 cur_temp; s32 avg_temp; /* private data */ u64 *freq_stats[4]; u64 freq_stats0_sum; u64 freq_stats_ratio; u64 freq_stats0_avg; u64 freq_stats1_sum; int llc_status; }; static struct profiler { struct device_node *root; int enabled; s32 migov_id; u32 cal_id; u32 devfreq_type; struct freq_table *table; u32 table_cnt; u32 dyn_pwr_coeff; u32 st_pwr_coeff; const char *tz_name; /* currently do not use in MIF */ struct thermal_zone_device *tz; /* currently do not use in MIF */ struct freq_cstate fc; /* latest time_in_state info */ struct freq_cstate_snapshot fc_snap[NUM_OF_USER]; /* previous time_in_state info */ #if defined(CONFIG_SOC_S5E9925_EVT0) || defined(CONFIG_SOC_S5E8825) u64 *freq_stats[4]; u64 *freq_stats_snap[4]; #else struct exynos_wow_profile *prev_profile; #endif u32 cur_freq_idx; /* current freq_idx */ u32 max_freq_idx; /* current max_freq_idx */ u32 min_freq_idx; /* current min_freq_idx */ struct profile_result result[NUM_OF_USER]; struct kobject kobj; struct exynos_devfreq_freq_infos freq_infos; } profiler; /************************************************************************ * HELPER * ************************************************************************/ static inline void calc_delta(u64 *result_table, u64 *prev_table, u64 *cur_table, int size) { int i; u64 delta, cur; for (i = 0; i < size; i++) { cur = cur_table[i]; delta = cur - prev_table[i]; result_table[i] = delta; prev_table[i] = cur; } } /************************************************************************ * SUPPORT-MIGOV * ************************************************************************/ u32 mifpro_get_table_cnt(s32 id) { return profiler.table_cnt; } u32 mifpro_get_freq_table(s32 id, u32 *table) { int idx; for (idx = 0; idx < profiler.table_cnt; idx++) table[idx] = profiler.table[idx].freq; return idx; } u32 mifpro_get_max_freq(s32 id) { return exynos_pm_qos_request(profiler.freq_infos.pm_qos_class_max); } u32 mifpro_get_min_freq(s32 id) { return exynos_pm_qos_request(profiler.freq_infos.pm_qos_class); } u32 mifpro_get_freq(s32 id) { return profiler.result[MIGOV].fc_result.freq[ACTIVE]; } void mifpro_get_power(s32 id, u64 *dyn_power, u64 *st_power) { *dyn_power = profiler.result[MIGOV].fc_result.dyn_power; *st_power = profiler.result[MIGOV].fc_result.st_power; } void mifpro_get_power_change(s32 id, s32 freq_delta_ratio, u32 *freq, u64 *dyn_power, u64 *st_power) { struct profile_result *result = &profiler.result[MIGOV]; struct freq_cstate_result *fc_result = &result->fc_result; int flag = (STATE_SCALE_WO_SPARE | STATE_SCALE_CNT); u64 dyn_power_backup; get_power_change(profiler.table, profiler.table_cnt, profiler.cur_freq_idx, profiler.min_freq_idx, profiler.max_freq_idx, result->freq_stats[0], fc_result->time[CLK_OFF], freq_delta_ratio, fc_result->profile_time, result->avg_temp, flag, dyn_power, st_power, freq); dyn_power_backup = *dyn_power; get_power_change(profiler.table, profiler.table_cnt, profiler.cur_freq_idx, profiler.min_freq_idx, profiler.max_freq_idx, fc_result->time[ACTIVE], fc_result->time[CLK_OFF], freq_delta_ratio, fc_result->profile_time, result->avg_temp, flag, dyn_power, st_power, freq); *dyn_power = dyn_power_backup; } u32 mifpro_get_active_pct(s32 id) { return profiler.result[MIGOV].fc_result.ratio[ACTIVE]; } s32 mifpro_get_temp(s32 id) { return profiler.result[MIGOV].avg_temp; } void mifpro_set_margin(s32 id, s32 margin) { return; } u32 mifpro_update_profile(int user); u32 mifpro_update_mode(s32 id, int mode) { int i; if (profiler.enabled == 0 && mode == 1) { struct freq_cstate *fc = &profiler.fc; struct freq_cstate_snapshot *fc_snap = &profiler.fc_snap[MIGOV]; sync_fcsnap_with_cur(fc, fc_snap, profiler.table_cnt); #if defined(CONFIG_SOC_S5E9925_EVT0) || defined(CONFIG_SOC_S5E8825) exynos_bcm_calc_enable(1); #endif profiler.enabled = mode; exynos_devfreq_set_profile(profiler.devfreq_type, 1); } else if (profiler.enabled == 1 && mode == 0) { exynos_devfreq_set_profile(profiler.devfreq_type, 0); #if defined(CONFIG_SOC_S5E9925_EVT0) || defined(CONFIG_SOC_S5E8825) exynos_bcm_calc_enable(0); #endif profiler.enabled = mode; // clear for (i = 0; i < NUM_OF_CSTATE; i++) { memset(profiler.result[MIGOV].fc_result.time[i], 0, sizeof(ktime_t) * profiler.table_cnt); profiler.result[MIGOV].fc_result.ratio[i] = 0; profiler.result[MIGOV].fc_result.freq[i] = 0; memset(profiler.fc.time[i], 0, sizeof(ktime_t) * profiler.table_cnt); memset(profiler.fc_snap[MIGOV].time[i], 0, sizeof(ktime_t) * profiler.table_cnt); } profiler.result[MIGOV].fc_result.dyn_power = 0; profiler.result[MIGOV].fc_result.st_power = 0; profiler.result[MIGOV].fc_result.profile_time = 0; profiler.result[MIGOV].freq_stats0_sum = 0; profiler.result[MIGOV].freq_stats0_avg = 0; profiler.result[MIGOV].freq_stats1_sum = 0; profiler.result[MIGOV].freq_stats_ratio = 0; profiler.fc_snap[MIGOV].last_snap_time = 0; #if defined(CONFIG_SOC_S5E9925_EVT0) || defined(CONFIG_SOC_S5E8825) for (i = 0; i < 4; i++) { memset(profiler.freq_stats[i], 0, sizeof(u64) * profiler.table_cnt); memset(profiler.freq_stats_snap[i], 0, sizeof(u64) * profiler.table_cnt); memset(profiler.result[MIGOV].freq_stats[i], 0, sizeof(u64) * profiler.table_cnt); } #else memset(profiler.prev_profile, 0, sizeof(struct exynos_wow_profile) * profiler.table_cnt); #endif return 0; } mifpro_update_profile(MIGOV); return 0; } u64 mifpro_get_freq_stats0_sum(void) { return profiler.result[MIGOV].freq_stats0_sum; }; u64 mifpro_get_freq_stats1_sum(void) { return profiler.result[MIGOV].freq_stats1_sum; }; u64 mifpro_get_freq_stats0_avg(void) { return profiler.result[MIGOV].freq_stats0_avg; }; u64 mifpro_get_freq_stats_ratio(void) { return profiler.result[MIGOV].freq_stats_ratio; }; u64 mifpro_get_llc_status(void) { return profiler.result[MIGOV].llc_status; }; struct private_fn_mif mif_pd_fn = { .get_stats0_sum = &mifpro_get_freq_stats0_sum, .get_stats0_avg = &mifpro_get_freq_stats0_avg, .get_stats1_sum = &mifpro_get_freq_stats1_sum, .get_stats_ratio = &mifpro_get_freq_stats_ratio, .get_llc_status = &mifpro_get_llc_status, }; struct domain_fn mif_fn = { .get_table_cnt = &mifpro_get_table_cnt, .get_freq_table = &mifpro_get_freq_table, .get_max_freq = &mifpro_get_max_freq, .get_min_freq = &mifpro_get_min_freq, .get_freq = &mifpro_get_freq, .get_power = &mifpro_get_power, .get_power_change = &mifpro_get_power_change, .get_active_pct = &mifpro_get_active_pct, .get_temp = &mifpro_get_temp, .set_margin = &mifpro_set_margin, .update_mode = &mifpro_update_mode, }; /************************************************************************ * Gathering MIFFreq Information * ************************************************************************/ //ktime_t * exynos_stats_get_mif_time_in_state(void); extern u64 exynos_bcm_get_ccnt(unsigned int idx); u32 mifpro_update_profile(int user) { struct freq_cstate *fc = &profiler.fc; struct freq_cstate_snapshot *fc_snap = &profiler.fc_snap[user]; struct freq_cstate_result *fc_result = &profiler.result[user].fc_result; struct profile_result *result = &profiler.result[user]; int i; u64 total_active_time = 0, freq_stats2_sum = 0, freq_stats3_sum = 0, diff_ccnt = 0; #if defined(CONFIG_SOC_S5E9925_EVT0) || defined(CONFIG_SOC_S5E8825) u64 ccnt = 0; static u64 prev_ccnt = 0; #else struct exynos_wow_profile *profile_in_state; #endif profiler.cur_freq_idx = get_idx_from_freq(profiler.table, profiler.table_cnt, *(profiler.freq_infos.cur_freq), RELATION_LOW); profiler.max_freq_idx = get_idx_from_freq(profiler.table, profiler.table_cnt, exynos_pm_qos_request(profiler.freq_infos.pm_qos_class_max), RELATION_LOW); profiler.min_freq_idx = get_idx_from_freq(profiler.table, profiler.table_cnt, exynos_pm_qos_request(profiler.freq_infos.pm_qos_class), RELATION_HIGH); #if defined(CONFIG_SOC_S5E9925_EVT0) || defined(CONFIG_SOC_S5E8825) // Update time in state and get tables from DVFS driver exynos_devfreq_get_profile(profiler.devfreq_type, fc->time, profiler.freq_stats); // calculate delta from previous status make_snapshot_and_time_delta(fc, fc_snap, fc_result, profiler.table_cnt); for (i = 0; i < 4; i++) calc_delta(result->freq_stats[i], profiler.freq_stats_snap[i], profiler.freq_stats[i], profiler.table_cnt); // Call to calc power compute_freq_cstate_result(profiler.table, fc_result, profiler.table_cnt, profiler.cur_freq_idx, result->avg_temp); ccnt = exynos_bcm_get_ccnt(0); diff_ccnt = ccnt - prev_ccnt; prev_ccnt = ccnt; #else profile_in_state = kzalloc(sizeof(struct exynos_wow_profile) * profiler.table_cnt, GFP_KERNEL); exynos_devfreq_get_profile(profiler.devfreq_type, fc->time, profile_in_state); // calculate delta from previous status make_snapshot_and_time_delta(fc, fc_snap, fc_result, profiler.table_cnt); // Call to calc power compute_freq_cstate_result(profiler.table, fc_result, profiler.table_cnt, profiler.cur_freq_idx, result->avg_temp); // Calculate freq_stats array for (i = 0; i < profiler.table_cnt; i++) { result->freq_stats[0][i] = (profile_in_state[i].transfer_data - profiler.prev_profile[i].transfer_data) >> 20; result->freq_stats[2][i] = (profile_in_state[i].nr_requests - profiler.prev_profile[i].nr_requests); result->freq_stats[3][i] = (profile_in_state[i].mo_count - profiler.prev_profile[i].mo_count); diff_ccnt += (profile_in_state[i].ccnt - profiler.prev_profile[i].ccnt); } memcpy(profiler.prev_profile, profile_in_state, sizeof(struct exynos_wow_profile) * profiler.table_cnt); kfree(profile_in_state); diff_ccnt /= 1000; #endif for (i = 0 ; i < profiler.table_cnt; i++) fc->time[CLK_OFF][i] -= fc->time[ACTIVE][i]; result->freq_stats0_sum = 0; result->freq_stats1_sum = 0; fc_result->dyn_power = 0; for (i = 0; i < profiler.table_cnt; i++) { result->freq_stats0_sum += result->freq_stats[0][i]; result->freq_stats1_sum += result->freq_stats[1][i]; result->freq_stats0_avg += (result->freq_stats[0][i] << 40) / (profiler.table[i].freq / 1000); total_active_time += fc_result->time[ACTIVE][i]; freq_stats2_sum += result->freq_stats[2][i]; freq_stats3_sum += result->freq_stats[3][i]; fc_result->dyn_power += ((result->freq_stats[0][i] * profiler.table[i].dyn_cost) / fc_result->profile_time); } result->freq_stats0_sum = result->freq_stats0_sum * 1000000000 / fc_result->profile_time; result->freq_stats1_sum = result->freq_stats1_sum * 1000000000 / fc_result->profile_time; result->freq_stats0_avg = result->freq_stats0_avg / total_active_time; result->freq_stats_ratio = fc_result->profile_time * freq_stats3_sum / freq_stats2_sum / diff_ccnt; result->llc_status = llc_get_en(); if (profiler.tz) { int temp = get_temp(profiler.tz); profiler.result[user].avg_temp = (temp + profiler.result[user].cur_temp) >> 1; profiler.result[user].cur_temp = temp; } return 0; } /************************************************************************ * INITIALIZATON * ************************************************************************/ /* Initialize profiler data */ static int register_export_fn(u32 *max_freq, u32 *min_freq, u32 *cur_freq) { struct exynos_devfreq_freq_infos *freq_infos = &profiler.freq_infos; exynos_devfreq_get_freq_infos(profiler.devfreq_type, freq_infos); // *max_freq = freq_infos->max_freq; /* get_org_max_freq(void) */ *max_freq = 3172000; *min_freq = freq_infos->min_freq; /* get_org_min_freq(void) */ *cur_freq = *freq_infos->cur_freq; /* get_cur_freq(void) */ profiler.table_cnt = freq_infos->max_state; /* get_freq_table_cnt(void) */ /* // 2020 profiler.fc.time[ACTIVE] = exynos_stats_get_mif_time_in_state(); // Olympus profiler.fc.time[ACTIVE] = get_time_inf_freq(ACTIVE); profiler.fc.time[CLKOFF] = get_time_in_freq(CLKOFF); */ return 0; } static int parse_dt(struct device_node *dn) { int ret; /* necessary data */ ret = of_property_read_u32(dn, "cal-id", &profiler.cal_id); if (ret) return -2; /* un-necessary data */ ret = of_property_read_s32(dn, "migov-id", &profiler.migov_id); if (ret) profiler.migov_id = -1; /* Don't support migov */ ret = of_property_read_s32(dn, "devfreq-type", &profiler.devfreq_type); if (ret) profiler.devfreq_type = -1; /* Don't support migov */ of_property_read_u32(dn, "power-coefficient", &profiler.dyn_pwr_coeff); of_property_read_u32(dn, "static-power-coefficient", &profiler.st_pwr_coeff); // of_property_read_string(dn, "tz-name", &profiler.tz_name); return 0; } static int init_profile_result(struct profile_result *result, int size) { int state; if (init_freq_cstate_result(&result->fc_result, NUM_OF_CSTATE, size)) return -ENOMEM; /* init private data */ for (state = 0; state < 4; state++) { ktime_t *state_in_freq; state_in_freq = alloc_state_in_freq(size); if (!state_in_freq) return -ENOMEM; result->freq_stats[state] = state_in_freq; } return 0; } #ifdef CONFIG_EXYNOS_DEBUG_INFO static void show_profiler_info(void) { int idx; pr_info("================ mif domain ================\n"); pr_info("min= %dKHz, max= %dKHz\n", profiler.table[profiler.table_cnt - 1].freq, profiler.table[0].freq); for (idx = 0; idx < profiler.table_cnt; idx++) pr_info("lv=%3d freq=%8d volt=%8d dyn_cost=%5d st_cost=%5d\n", idx, profiler.table[idx].freq, profiler.table[idx].volt, profiler.table[idx].dyn_cost, profiler.table[idx].st_cost); if (profiler.tz_name) pr_info("support temperature (tz_name=%s)\n", profiler.tz_name); if (profiler.migov_id != -1) pr_info("support migov domain(id=%d)\n", profiler.migov_id); } #endif static int exynos_mif_profiler_probe(struct platform_device *pdev) { unsigned int org_max_freq, org_min_freq, cur_freq; int ret, idx; #if defined(CONFIG_SOC_S5E9925_EVT0) || defined(CONFIG_SOC_S5E8825) int i; #endif /* get node of device tree */ if (!pdev->dev.of_node) { pr_err("mifpro: failed to get device treee\n"); return -EINVAL; } profiler.root = pdev->dev.of_node; /* Parse data from Device Tree to init domain */ ret = parse_dt(profiler.root); if (ret) { pr_err("mifpro: failed to parse dt(ret: %d)\n", ret); return -EINVAL; } register_export_fn(&org_max_freq, &org_min_freq, &cur_freq); /* init freq table */ profiler.table = init_freq_table(NULL, profiler.table_cnt, profiler.cal_id, org_max_freq, org_min_freq, profiler.dyn_pwr_coeff, profiler.st_pwr_coeff, PWR_COST_CVV, PWR_COST_CVV); if (!profiler.table) { pr_err("mifpro: failed to init freq_table\n"); return -EINVAL; } profiler.max_freq_idx = 0; profiler.min_freq_idx = profiler.table_cnt - 1; profiler.cur_freq_idx = get_idx_from_freq(profiler.table, profiler.table_cnt, cur_freq, RELATION_HIGH); if (init_freq_cstate(&profiler.fc, NUM_OF_CSTATE, profiler.table_cnt)) return -ENOMEM; /* init snapshot & result table */ for (idx = 0; idx < NUM_OF_USER; idx++) { if (init_freq_cstate_snapshot(&profiler.fc_snap[idx], NUM_OF_CSTATE, profiler.table_cnt)) return -ENOMEM; if (init_profile_result(&profiler.result[idx], profiler.table_cnt)) return -EINVAL; #if defined(CONFIG_SOC_S5E9925_EVT0) || defined(CONFIG_SOC_S5E8825) for (i = 0 ; i < 4; i++) { profiler.freq_stats[i] = kzalloc(sizeof(u64) * profiler.table_cnt, GFP_KERNEL); profiler.freq_stats_snap[i] = kzalloc(sizeof(u64) * profiler.table_cnt, GFP_KERNEL); } #else profiler.prev_profile = kzalloc(sizeof(struct exynos_wow_profile) * profiler.table_cnt, GFP_KERNEL); #endif } /* get thermal-zone to get temperature */ if (profiler.tz_name) profiler.tz = init_temp(profiler.tz_name); if (profiler.tz) init_static_cost(profiler.table, profiler.table_cnt, 1, profiler.root, profiler.tz); ret = exynos_migov_register_domain(MIGOV_MIF, &mif_fn, &mif_pd_fn); #ifdef CONFIG_EXYNOS_DEBUG_INFO show_profiler_info(); #endif return ret; } static const struct of_device_id exynos_mif_profiler_match[] = { { .compatible = "samsung,exynos-mif-profiler", }, {}, }; MODULE_DEVICE_TABLE(of, exynos_mif_profiler_match); static struct platform_driver exynos_mif_profiler_driver = { .probe = exynos_mif_profiler_probe, .driver = { .name = "exynos-mif-profiler", .owner = THIS_MODULE, .of_match_table = exynos_mif_profiler_match, }, }; static int exynos_mif_profiler_init(void) { return platform_driver_register(&exynos_mif_profiler_driver); } late_initcall(exynos_mif_profiler_init); MODULE_DESCRIPTION("Exynos MIF Profiler"); MODULE_LICENSE("GPL");