blob: eb01b6a7e9dadd3ebe2dcf81d9527efd296eabe7 [file] [log] [blame]
/* linux/drivers/devfreq/exynos/exynos7885_bus_mif.c
*
* Copyright (c) 2015 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Samsung EXYNOS7885 SoC MIF 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/init.h>
#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 <linux/workqueue.h>
#include <soc/samsung/exynos-devfreq.h>
#include <linux/apm-exynos.h>
#include <soc/samsung/asv-exynos.h>
#include <linux/mcu_ipc.h>
#include <linux/mfd/samsung/core.h>
#include <soc/samsung/cal-if.h>
#include "../governor.h"
#include <soc/samsung/exynos-dm.h>
#include <soc/samsung/ect_parser.h>
#include "../../soc/samsung/acpm/acpm.h"
#include "../../soc/samsung/acpm/acpm_ipc.h"
#include "../../soc/samsung/cal-if/acpm_dvfs.h"
#include "exynos_ppmu.h"
#define INT 0
#ifdef CONFIG_EXYNOS_DVFS_MANAGER
static unsigned int ect_find_constraint_freq(struct ect_minlock_domain *ect_domain,
unsigned int freq)
{
unsigned int i;
for (i =0; i < ect_domain->num_of_level; i++)
if (ect_domain->level[i].main_frequencies == freq) break;
return ect_domain->level[i].sub_frequencies;
}
#endif
static int exynos7885_mif_constraint_parse(struct exynos_devfreq_data *data,
unsigned int min_freq, unsigned int max_freq)
{
int i;
int ret;
int ch_num;
int size;
int use_level = 0;
int const_flag = 1;
unsigned int cmd[4];
struct ipc_config config;
void *min_block;
void *dvfs_block;
struct ect_dvfs_domain *dvfs_domain;
struct ect_minlock_domain *ect_domain;
#ifdef CONFIG_EXYNOS_DVFS_MANAGER
struct exynos_dm_freq *const_table;
#endif
dvfs_block = ect_get_block(BLOCK_DVFS);
if (dvfs_block == NULL)
return -ENODEV;
dvfs_domain = ect_dvfs_get_domain(dvfs_block, "dvfs_mif");
if (dvfs_domain == NULL)
return -ENODEV;
/* Although there is not any constraint, MIF table should be sent to FVP */
min_block = ect_get_block(BLOCK_MINLOCK);
if (min_block == NULL) {
dev_info(data->dev, "There is not a min block in ECT\n");
const_flag = 0;
}
ect_domain = ect_minlock_get_domain(min_block, "dvfs_mif");
if (ect_domain == NULL) {
dev_info(data->dev, "There is not a domain in min block\n");
const_flag = 0;
}
#ifdef CONFIG_EXYNOS_DVFS_MANAGER
if(const_flag) {
data->constraint[INT] = kzalloc(sizeof(struct exynos_dm_constraint), GFP_KERNEL);
if (data->constraint[INT] == NULL) {
dev_err(data->dev, "failed to allocate constraint\n");
return -ENOMEM;
}
const_table = kzalloc(sizeof(struct exynos_dm_freq) * ect_domain->num_of_level, GFP_KERNEL);
if (const_table == NULL) {
dev_err(data->dev, "failed to allocate constraint\n");
kfree(data->constraint[INT]);
return -ENOMEM;
}
data->constraint[INT]->guidance = true;
data->constraint[INT]->constraint_type = CONSTRAINT_MIN;
data->constraint[INT]->constraint_dm_type = DM_INT;
data->constraint[INT]->table_length = ect_domain->num_of_level;
data->constraint[INT]->freq_table = const_table;
}
#endif
ret = acpm_ipc_request_channel(data->dev->of_node, NULL, &ch_num, &size);
if (ret) {
dev_err(data->dev, "acpm request channel is failed, id:%u, size:%u\n", ch_num, size);
return -EINVAL;
}
config.cmd = cmd;
config.response = true;
config.indirection = false;
for (i = 0; i < dvfs_domain->num_of_level; i++) {
if (data->opp_list[i].freq > max_freq ||
data->opp_list[i].freq < min_freq)
continue;
config.cmd[0] = use_level;
config.cmd[1] = data->opp_list[i].freq;
config.cmd[2] = DATA_INIT;
config.cmd[3] = 0;
#ifdef CONFIG_EXYNOS_DVFS_MANAGER
if (const_flag) {
const_table[use_level].master_freq = data->opp_list[i].freq;
const_table[use_level].constraint_freq
= ect_find_constraint_freq(ect_domain, data->opp_list[i].freq);
config.cmd[3] = const_table[use_level].constraint_freq;
}
#endif
ret = acpm_ipc_send_data(ch_num, &config);
if (ret) {
dev_err(data->dev, "make constraint table is failed");
return -EINVAL;
}
use_level++;
}
/* Send MIF initial freq and the number of constraint data to FVP */
if (const_flag) {
config.cmd[0] = use_level;
config.cmd[1] = data->devfreq_profile.initial_freq;
config.cmd[2] = DATA_INIT;
config.cmd[3] = SET_CONST;
ret = acpm_ipc_send_data(ch_num, &config);
if (ret) {
dev_err(data->dev, "failed to send nr_constraint and init freq");
return -EINVAL;
}
}
return 0;
}
static int exynos7885_devfreq_mif_cmu_dump(struct exynos_devfreq_data *data)
{
mutex_lock(&data->devfreq->lock);
cal_vclk_dbg_info(data->dfs_id);
mutex_unlock(&data->devfreq->lock);
return 0;
}
static int exynos7885_devfreq_mif_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_mif_resume(struct exynos_devfreq_data *data)
{
u32 cur_freq;
if (exynos7885_devfreq_mif_get_freq(data->dev, &cur_freq, data->clk, data)) {
dev_err(data->dev, "failed get frequency when resume\n");
}
#ifndef CONFIG_EXYNOS_DVFS_MANAGER
if (pm_qos_request_active(&data->default_pm_qos_max))
pm_qos_update_request(&data->default_pm_qos_max, data->max_freq);
#endif
dev_info(data->dev, "Resume frequency is %u\n", cur_freq);
return 0;
}
static int exynos7885_devfreq_mif_suspend(struct exynos_devfreq_data *data)
{
#ifndef CONFIG_EXYNOS_DVFS_MANAGER
if (pm_qos_request_active(&data->default_pm_qos_max))
pm_qos_update_request(&data->default_pm_qos_max,
data->devfreq_profile.suspend_freq);
#endif
return 0;
}
static int exynos7885_devfreq_mif_reboot(struct exynos_devfreq_data *data)
{
if (pm_qos_request_active(&data->default_pm_qos_max))
pm_qos_update_request(&data->default_pm_qos_max,
data->reboot_freq);
return 0;
}
static int exynos7885_devfreq_mif_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_mif_init_freq_table(struct exynos_devfreq_data *data)
{
u32 max_freq, min_freq, cur_freq;
unsigned long tmp_max, tmp_min;
struct dev_pm_opp *target_opp;
u32 flags = 0;
int i, ret;
ret = cal_clk_enable(data->dfs_id);
if (ret) {
dev_err(data->dev, "failed to enable MIF\n");
return -EINVAL;
}
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);
cur_freq = clk_get_rate(data->clk);
dev_info(data->dev, "current frequency: %uKhz\n", cur_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 = exynos7885_mif_constraint_parse(data, min_freq, max_freq);
if (ret) {
dev_err(data->dev, "failed to parse constraint table\n");
return -EINVAL;
}
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_mif_um_register(struct exynos_devfreq_data *data)
{
#ifdef CONFIG_EXYNOS_WD_DVFS
int i;
if (data->use_get_dev) {
for (i = 0; i < data->um_data.um_count; i++)
exynos_init_ppmu(data->um_data.va_base[i],
data->um_data.mask_v[i],
data->um_data.mask_a[i]);
for (i = 0; i < data->um_data.um_count; i++)
exynos_start_ppmu(data->um_data.va_base[i]);
}
#endif
return 0;
}
static int exynos7885_devfreq_mif_um_unregister(struct exynos_devfreq_data *data)
{
#ifdef CONFIG_EXYNOS_WD_DVFS
int i;
if (data->use_get_dev) {
for (i = 0; i < data->um_data.um_count; i++)
exynos_exit_ppmu(data->um_data.va_base[i]);
}
#endif
return 0;
}
static int exynos7885_devfreq_mif_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 (!i)
data->um_data.val_ccnt = ppmu.ccnt;
if (max < ppmu.pmcnt0)
max = ppmu.pmcnt0;
if (max < ppmu.pmcnt1)
max = ppmu.pmcnt1;
}
data->um_data.val_pmcnt = max;
#endif
return 0;
}
static int __init exynos7885_devfreq_mif_init_prepare(struct exynos_devfreq_data *data)
{
data->ops.um_register = exynos7885_devfreq_mif_um_register;
data->ops.um_unregister = exynos7885_devfreq_mif_um_unregister;
data->ops.get_dev_status = exynos7885_devfreq_mif_get_status;
data->ops.get_freq = exynos7885_devfreq_mif_get_freq;
data->ops.set_freq = exynos7885_devfreq_mif_set_freq;
data->ops.init_freq_table = exynos7885_devfreq_mif_init_freq_table;
data->ops.suspend = exynos7885_devfreq_mif_suspend;
data->ops.reboot = exynos7885_devfreq_mif_reboot;
data->ops.resume = exynos7885_devfreq_mif_resume;
data->ops.cmu_dump = exynos7885_devfreq_mif_cmu_dump;
return 0;
}
static int __init exynos7885_devfreq_mif_initcall(void)
{
if (register_exynos_devfreq_init_prepare(DEVFREQ_MIF,
exynos7885_devfreq_mif_init_prepare))
return -EINVAL;
return 0;
}
fs_initcall(exynos7885_devfreq_mif_initcall);