| /* linux/drivers/devfreq/exynos/exynos7885_bus_int.c |
| * |
| * Copyright (c) 2015 Samsung Electronics Co., Ltd. |
| * http://www.samsung.com |
| * |
| * Samsung EXYNOS7885 SoC INT devfreq driver |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published |
| * by the Free Software Foundation, either version 2 of the License, |
| * or (at your option) any later version. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/version.h> |
| #include <linux/types.h> |
| #include <linux/errno.h> |
| #include <linux/device.h> |
| #include <linux/platform_device.h> |
| #include <linux/list.h> |
| #include <linux/clk.h> |
| |
| #include <soc/samsung/exynos-devfreq.h> |
| #include <sound/samsung/abox.h> |
| #include <soc/samsung/cal-if.h> |
| #include "../../soc/samsung/cal-if/acpm_dvfs.h" |
| #include "../governor.h" |
| |
| #include "exynos_ppmu.h" |
| |
| static int exynos7885_devfreq_int_cmu_dump(struct exynos_devfreq_data *data) |
| { |
| cal_vclk_dbg_info(data->dfs_id); |
| |
| return 0; |
| } |
| |
| static int exynos7885_devfreq_int_reboot(struct exynos_devfreq_data *data) |
| { |
| data->max_freq = data->reboot_freq; |
| data->devfreq->max_freq = data->max_freq; |
| |
| mutex_lock(&data->devfreq->lock); |
| update_devfreq(data->devfreq); |
| mutex_unlock(&data->devfreq->lock); |
| |
| return 0; |
| } |
| |
| static int exynos7885_devfreq_int_get_freq(struct device *dev, u32 *cur_freq, |
| struct clk *clk, struct exynos_devfreq_data *data) |
| { |
| *cur_freq = (u32)cal_dfs_get_rate(data->dfs_id); |
| if (*cur_freq == 0) { |
| dev_err(dev, "failed get frequency from CAL\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int exynos7885_devfreq_int_set_freq(struct device *dev, u32 new_freq, |
| struct clk *clk, struct exynos_devfreq_data *data) |
| { |
| if (cal_dfs_set_rate(data->dfs_id, (unsigned long)new_freq)) { |
| dev_err(dev, "failed set frequency to CAL (%uKhz)\n", |
| new_freq); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int exynos7885_devfreq_int_resume(struct exynos_devfreq_data *data) |
| { |
| u32 cur_freq; |
| |
| /* for sync from resume frequency */ |
| if (exynos7885_devfreq_int_get_freq(data->dev, &cur_freq, data->clk, data)) { |
| dev_err(data->dev, "failed get frequency when resume\n"); |
| return -EINVAL; |
| } |
| |
| dev_info(data->dev, "Resume frequency is %u\n", cur_freq); |
| |
| return 0; |
| } |
| |
| static int exynos7885_devfreq_int_init_freq_table(struct exynos_devfreq_data *data) |
| { |
| u32 max_freq, min_freq; |
| unsigned long tmp_max, tmp_min; |
| struct dev_pm_opp *target_opp; |
| u32 flags = 0; |
| int i, ret = 0; |
| |
| max_freq = (u32)cal_dfs_get_max_freq(data->dfs_id); |
| if (!max_freq) { |
| dev_err(data->dev, "failed get max frequency\n"); |
| return -EINVAL; |
| } |
| |
| dev_info(data->dev, "max_freq: %uKhz, get_max_freq: %uKhz\n", |
| data->max_freq, max_freq); |
| |
| if (max_freq < data->max_freq) { |
| rcu_read_lock(); |
| flags |= DEVFREQ_FLAG_LEAST_UPPER_BOUND; |
| tmp_max = (unsigned long)max_freq; |
| target_opp = devfreq_recommended_opp(data->dev, &tmp_max, flags); |
| if (IS_ERR(target_opp)) { |
| rcu_read_unlock(); |
| dev_err(data->dev, "not found valid OPP for max_freq\n"); |
| return PTR_ERR(target_opp); |
| } |
| |
| data->max_freq = dev_pm_opp_get_freq(target_opp); |
| rcu_read_unlock(); |
| } |
| |
| /* min ferquency must be equal or under max frequency */ |
| if (data->min_freq > data->max_freq) |
| data->min_freq = data->max_freq; |
| |
| min_freq = (u32)cal_dfs_get_min_freq(data->dfs_id); |
| if (!min_freq) { |
| dev_err(data->dev, "failed get min frequency\n"); |
| return -EINVAL; |
| } |
| |
| dev_info(data->dev, "min_freq: %uKhz, get_min_freq: %uKhz\n", |
| data->min_freq, min_freq); |
| |
| if (min_freq > data->min_freq) { |
| rcu_read_lock(); |
| flags &= ~DEVFREQ_FLAG_LEAST_UPPER_BOUND; |
| tmp_min = (unsigned long)min_freq; |
| target_opp = devfreq_recommended_opp(data->dev, &tmp_min, flags); |
| if (IS_ERR(target_opp)) { |
| rcu_read_unlock(); |
| dev_err(data->dev, "not found valid OPP for min_freq\n"); |
| return PTR_ERR(target_opp); |
| } |
| |
| data->min_freq = dev_pm_opp_get_freq(target_opp); |
| rcu_read_unlock(); |
| } |
| |
| dev_info(data->dev, "min_freq: %uKhz, max_freq: %uKhz\n", |
| data->min_freq, data->max_freq); |
| |
| for (i = 0; i < data->max_state; i++) { |
| if (data->opp_list[i].freq > data->max_freq || |
| data->opp_list[i].freq < data->min_freq) |
| dev_pm_opp_disable(data->dev, (unsigned long)data->opp_list[i].freq); |
| } |
| |
| data->devfreq_profile.initial_freq = cal_dfs_get_boot_freq(data->dfs_id); |
| data->devfreq_profile.suspend_freq = cal_dfs_get_resume_freq(data->dfs_id); |
| |
| ret = exynos_acpm_set_init_freq(data->dfs_id, data->devfreq_profile.initial_freq); |
| if (ret) { |
| dev_err(data->dev, "failed to set init freq\n"); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int exynos7885_devfreq_int_get_status(struct exynos_devfreq_data *data) |
| { |
| #ifdef CONFIG_EXYNOS_WD_DVFS |
| int i; |
| struct ppmu_data ppmu = { 0, }; |
| u64 max = 0; |
| |
| for (i = 0; i < data->um_data.um_count; i++) |
| exynos_reset_ppmu(data->um_data.va_base[i], |
| data->um_data.channel[i]); |
| |
| for (i = 0; i < data->um_data.um_count; i++) { |
| exynos_read_ppmu(&ppmu, data->um_data.va_base[i], |
| data->um_data.channel[i]); |
| if (max < ppmu.pmcnt2) |
| max = ppmu.pmcnt2; |
| if (max < ppmu.pmcnt3) |
| max = ppmu.pmcnt3; |
| } |
| data->um_data.val_pmcnt = max; |
| data->um_data.val_ccnt = (((u64)data->last_monitor_period) * |
| data->devfreq->previous_freq) / |
| (NSEC_PER_MSEC); |
| #endif |
| return 0; |
| } |
| |
| static int __init exynos7885_devfreq_int_init_prepare(struct exynos_devfreq_data *data) |
| { |
| data->ops.get_dev_status = exynos7885_devfreq_int_get_status; |
| data->ops.get_freq = exynos7885_devfreq_int_get_freq; |
| data->ops.set_freq = exynos7885_devfreq_int_set_freq; |
| data->ops.init_freq_table = exynos7885_devfreq_int_init_freq_table; |
| data->ops.resume = exynos7885_devfreq_int_resume; |
| data->ops.reboot = exynos7885_devfreq_int_reboot; |
| data->ops.cmu_dump = exynos7885_devfreq_int_cmu_dump; |
| |
| return 0; |
| } |
| |
| static int __init exynos7885_devfreq_int_initcall(void) |
| { |
| if (register_exynos_devfreq_init_prepare(DEVFREQ_INT, |
| exynos7885_devfreq_int_init_prepare)) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| fs_initcall(exynos7885_devfreq_int_initcall); |