blob: 6a466a8a429a877996982c876a5e25bd74a51abf [file] [log] [blame]
/* linux/drivers/soc/samsung/exynos-dm.c
*
* Copyright (C) 2016 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Samsung Exynos SoC series DVFS Manager
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/errno.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/exynos-ss.h>
#include "acpm/acpm.h"
#include "acpm/acpm_ipc.h"
#include <soc/samsung/exynos-dm.h>
static struct list_head *get_min_constraint_list(struct exynos_dm_data *dm_data);
static struct list_head *get_max_constraint_list(struct exynos_dm_data *dm_data);
static void get_governor_min_freq(struct exynos_dm_data *dm_data, u32 *gov_min_freq);
static void get_min_max_freq(struct exynos_dm_data *dm_data, u32 *min_freq, u32 *max_freq);
static void update_min_max_freq(struct exynos_dm_data *dm_data, u32 min_freq, u32 max_freq);
static void get_policy_min_max_freq(struct exynos_dm_data *dm_data, u32 *min_freq, u32 *max_freq);
static void update_policy_min_max_freq(struct exynos_dm_data *dm_data, u32 min_freq, u32 max_freq);
static void get_current_freq(struct exynos_dm_data *dm_data, u32 *cur_freq);
static void get_target_freq(struct exynos_dm_data *dm_data, u32 *target_freq);
#define DM_EMPTY 0xFF
static struct exynos_dm_device *exynos_dm;
static enum exynos_dm_type min_order[DM_TYPE_END + 1] = {DM_EMPTY, };
static enum exynos_dm_type max_order[DM_TYPE_END + 1] = {DM_EMPTY, };
/*
* SYSFS for Debugging
*/
static ssize_t show_available(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct platform_device *pdev = container_of(dev, struct platform_device, dev);
struct exynos_dm_device *dm = platform_get_drvdata(pdev);
ssize_t count = 0;
int i;
for (i = 0; i < DM_TYPE_END; i++) {
if (!dm->dm_data[i].available)
continue;
count += snprintf(buf + count, PAGE_SIZE,
"dm_type: %d(%s), dvfs_type: %d, available = %s\n",
dm->dm_data[i].dm_type, dm->dm_data[i].dm_type_name,
dm->dm_data[i].dvfs_type,
dm->dm_data[i].available ? "true" : "false");
}
return count;
}
#define show_constraint_tables(dm_type, type_name) \
static ssize_t show_constraint_tables_##type_name \
(struct device *dev, struct device_attribute *attr, char *buf) \
{ \
struct platform_device *pdev = container_of(dev, struct platform_device, dev); \
struct exynos_dm_device *dm = platform_get_drvdata(pdev); \
struct list_head *constraint_list; \
struct exynos_dm_constraint *constraint; \
ssize_t count = 0; \
int i; \
\
if (!dm->dm_data[dm_type].available) { \
count += snprintf(buf + count, PAGE_SIZE, \
"This dm_type is not available\n"); \
return count; \
} \
\
count += snprintf(buf + count, PAGE_SIZE, "dm_type: %s\n", \
dm->dm_data[dm_type].dm_type_name); \
\
constraint_list = get_min_constraint_list(&dm->dm_data[dm_type]); \
if (list_empty(constraint_list)) { \
count += snprintf(buf + count, PAGE_SIZE, \
"This dm_type have not min constraint tables\n\n"); \
goto next; \
} \
\
list_for_each_entry(constraint, constraint_list, node) { \
count += snprintf(buf + count, PAGE_SIZE, \
"-------------------------------------------------\n"); \
count += snprintf(buf + count, PAGE_SIZE, \
"constraint_dm_type = %s\n", constraint->dm_type_name); \
count += snprintf(buf + count, PAGE_SIZE, "constraint_type: %s\n", \
constraint->constraint_type ? "MAX" : "MIN"); \
count += snprintf(buf + count, PAGE_SIZE, "guidance: %s\n", \
constraint->guidance ? "true" : "false"); \
count += snprintf(buf + count, PAGE_SIZE, \
"min_freq = %u, max_freq =%u\n", \
constraint->min_freq, constraint->max_freq); \
count += snprintf(buf + count, PAGE_SIZE, \
"master_freq\t constraint_freq\n"); \
for (i = 0; i < constraint->table_length; i++) \
count += snprintf(buf + count, PAGE_SIZE, "%10u\t %10u\n", \
constraint->freq_table[i].master_freq, \
constraint->freq_table[i].constraint_freq); \
count += snprintf(buf + count, PAGE_SIZE, \
"-------------------------------------------------\n"); \
} \
\
next: \
constraint_list = get_max_constraint_list(&dm->dm_data[dm_type]); \
if (list_empty(constraint_list)) { \
count += snprintf(buf + count, PAGE_SIZE, \
"This dm_type have not max constraint tables\n\n"); \
return count; \
} \
\
list_for_each_entry(constraint, constraint_list, node) { \
count += snprintf(buf + count, PAGE_SIZE, \
"-------------------------------------------------\n"); \
count += snprintf(buf + count, PAGE_SIZE, \
"constraint_dm_type = %s\n", constraint->dm_type_name); \
count += snprintf(buf + count, PAGE_SIZE, "constraint_type: %s\n", \
constraint->constraint_type ? "MAX" : "MIN"); \
count += snprintf(buf + count, PAGE_SIZE, "guidance: %s\n", \
constraint->guidance ? "true" : "false"); \
count += snprintf(buf + count, PAGE_SIZE, \
"min_freq = %u, max_freq =%u\n", \
constraint->min_freq, constraint->max_freq); \
count += snprintf(buf + count, PAGE_SIZE, \
"master_freq\t constraint_freq\n"); \
for (i = 0; i < constraint->table_length; i++) \
count += snprintf(buf + count, PAGE_SIZE, "%10u\t %10u\n", \
constraint->freq_table[i].master_freq, \
constraint->freq_table[i].constraint_freq); \
count += snprintf(buf + count, PAGE_SIZE, \
"-------------------------------------------------\n"); \
} \
\
return count; \
}
#define show_dm_policy(dm_type, type_name) \
static ssize_t show_dm_policy_##type_name \
(struct device *dev, struct device_attribute *attr, char *buf) \
{ \
struct platform_device *pdev = container_of(dev, struct platform_device, dev); \
struct exynos_dm_device *dm = platform_get_drvdata(pdev); \
ssize_t count = 0; \
u32 gov_min_freq, min_freq, max_freq; \
u32 policy_min_freq, policy_max_freq, cur_freq, target_freq; \
\
if (!dm->dm_data[dm_type].available) { \
count += snprintf(buf + count, PAGE_SIZE, \
"This dm_type is not available\n"); \
return count; \
} \
\
count += snprintf(buf + count, PAGE_SIZE, "dm_type: %s\n", \
dm->dm_data[dm_type].dm_type_name); \
\
get_governor_min_freq(&dm->dm_data[dm_type], &gov_min_freq); \
get_min_max_freq(&dm->dm_data[dm_type], &min_freq, &max_freq); \
get_policy_min_max_freq(&dm->dm_data[dm_type], \
&policy_min_freq, &policy_max_freq); \
get_current_freq(&dm->dm_data[dm_type], &cur_freq); \
get_target_freq(&dm->dm_data[dm_type], &target_freq); \
\
count += snprintf(buf + count, PAGE_SIZE, \
"governor_min_freq = %u\n", gov_min_freq); \
count += snprintf(buf + count, PAGE_SIZE, \
"policy_min_freq = %u, policy_max_freq = %u\n", \
policy_min_freq, policy_max_freq); \
count += snprintf(buf + count, PAGE_SIZE, \
"min_freq = %u, max_freq = %u\n", min_freq, max_freq); \
count += snprintf(buf + count, PAGE_SIZE, "current_freq = %u\n", cur_freq); \
count += snprintf(buf + count, PAGE_SIZE, "target_freq = %u\n", target_freq); \
\
return count; \
}
show_constraint_tables(DM_CPU_CL0, dm_cpu_cl0);
show_constraint_tables(DM_CPU_CL1, dm_cpu_cl1);
show_constraint_tables(DM_MIF, dm_mif);
show_constraint_tables(DM_INT, dm_int);
show_constraint_tables(DM_INTCAM, dm_intcam);
show_constraint_tables(DM_DISP, dm_disp);
#if defined(CONFIG_SOC_EXYNOS7885)
show_constraint_tables(DM_FSYS, dm_fsys);
show_constraint_tables(DM_AUD, dm_aud);
#endif
show_constraint_tables(DM_CAM, dm_cam);
show_constraint_tables(DM_GPU, dm_gpu);
show_dm_policy(DM_CPU_CL0, dm_cpu_cl0);
show_dm_policy(DM_CPU_CL1, dm_cpu_cl1);
show_dm_policy(DM_MIF, dm_mif);
show_dm_policy(DM_INT, dm_int);
show_dm_policy(DM_INTCAM, dm_intcam);
show_dm_policy(DM_DISP, dm_disp);
#if defined(CONFIG_SOC_EXYNOS7885)
show_dm_policy(DM_FSYS, dm_fsys);
show_dm_policy(DM_AUD, dm_aud);
#endif
show_dm_policy(DM_CAM, dm_cam);
show_dm_policy(DM_GPU, dm_gpu);
static DEVICE_ATTR(available, 0440, show_available, NULL);
static DEVICE_ATTR(constraint_tables_dm_cpu_cl0, 0440, show_constraint_tables_dm_cpu_cl0, NULL);
static DEVICE_ATTR(constraint_tables_dm_cpu_cl1, 0440, show_constraint_tables_dm_cpu_cl1, NULL);
static DEVICE_ATTR(constraint_tables_dm_mif, 0440, show_constraint_tables_dm_mif, NULL);
static DEVICE_ATTR(constraint_tables_dm_int, 0440, show_constraint_tables_dm_int, NULL);
static DEVICE_ATTR(constraint_tables_dm_intcam, 0440, show_constraint_tables_dm_intcam, NULL);
static DEVICE_ATTR(constraint_tables_dm_disp, 0440, show_constraint_tables_dm_disp, NULL);
#if defined(CONFIG_SOC_EXYNOS7885)
static DEVICE_ATTR(constraint_tables_dm_fsys, 0440, show_constraint_tables_dm_fsys, NULL);
static DEVICE_ATTR(constraint_tables_dm_aud, 0440, show_constraint_tables_dm_aud, NULL);
#endif
static DEVICE_ATTR(constraint_tables_dm_cam, 0440, show_constraint_tables_dm_cam, NULL);
static DEVICE_ATTR(constraint_tables_dm_gpu, 0440, show_constraint_tables_dm_gpu, NULL);
static DEVICE_ATTR(dm_policy_dm_cpu_cl0, 0440, show_dm_policy_dm_cpu_cl0, NULL);
static DEVICE_ATTR(dm_policy_dm_cpu_cl1, 0440, show_dm_policy_dm_cpu_cl1, NULL);
static DEVICE_ATTR(dm_policy_dm_mif, 0440, show_dm_policy_dm_mif, NULL);
static DEVICE_ATTR(dm_policy_dm_int, 0440, show_dm_policy_dm_int, NULL);
static DEVICE_ATTR(dm_policy_dm_intcam, 0440, show_dm_policy_dm_intcam, NULL);
static DEVICE_ATTR(dm_policy_dm_disp, 0440, show_dm_policy_dm_disp, NULL);
#if defined(CONFIG_SOC_EXYNOS7885)
static DEVICE_ATTR(dm_policy_dm_fsys, 0440, show_dm_policy_dm_fsys, NULL);
static DEVICE_ATTR(dm_policy_dm_aud, 0440, show_dm_policy_dm_aud, NULL);
#endif
static DEVICE_ATTR(dm_policy_dm_cam, 0440, show_dm_policy_dm_cam, NULL);
static DEVICE_ATTR(dm_policy_dm_gpu, 0440, show_dm_policy_dm_gpu, NULL);
static struct attribute *exynos_dm_sysfs_entries[] = {
&dev_attr_available.attr,
&dev_attr_constraint_tables_dm_cpu_cl0.attr,
&dev_attr_constraint_tables_dm_cpu_cl1.attr,
&dev_attr_constraint_tables_dm_mif.attr,
&dev_attr_constraint_tables_dm_int.attr,
&dev_attr_constraint_tables_dm_intcam.attr,
&dev_attr_constraint_tables_dm_disp.attr,
#if defined(CONFIG_SOC_EXYNOS7885)
&dev_attr_constraint_tables_dm_fsys.attr,
&dev_attr_constraint_tables_dm_aud.attr,
#endif
&dev_attr_constraint_tables_dm_cam.attr,
&dev_attr_constraint_tables_dm_gpu.attr,
&dev_attr_dm_policy_dm_cpu_cl0.attr,
&dev_attr_dm_policy_dm_cpu_cl1.attr,
&dev_attr_dm_policy_dm_mif.attr,
&dev_attr_dm_policy_dm_int.attr,
&dev_attr_dm_policy_dm_intcam.attr,
&dev_attr_dm_policy_dm_disp.attr,
#if defined(CONFIG_SOC_EXYNOS7885)
&dev_attr_dm_policy_dm_fsys.attr,
&dev_attr_dm_policy_dm_aud.attr,
#endif
&dev_attr_dm_policy_dm_cam.attr,
&dev_attr_dm_policy_dm_gpu.attr,
NULL,
};
static struct attribute_group exynos_dm_attr_group = {
.name = "exynos_dm",
.attrs = exynos_dm_sysfs_entries,
};
/*
* SYSFS for Debugging end
*/
static void print_available_dm_data(struct exynos_dm_device *dm)
{
int i;
for (i = 0; i < DM_TYPE_END; i++) {
if (!dm->dm_data[i].available)
continue;
dev_info(dm->dev, "dm_type: %d(%s), dvfs_type: %d, available = %s\n",
dm->dm_data[i].dm_type, dm->dm_data[i].dm_type_name,
dm->dm_data[i].dvfs_type,
dm->dm_data[i].available ? "true" : "false");
}
}
static int exynos_dm_index_validate(enum exynos_dm_type index)
{
if ((index < DM_CPU_CL0) || (index >= DM_TYPE_END)) {
dev_err(exynos_dm->dev, "invalid dm_index (%d)\n", index);
return -EINVAL;
}
return 0;
}
static enum exynos_dvfs_type exynos_dm_dvfs_type(enum exynos_dm_type dm_type)
{
enum exynos_dvfs_type dvfs_type;
switch (dm_type) {
case DM_CPU_CL0...DM_CPU_CL1:
dvfs_type = DVFS_CPUFREQ;
break;
case DM_MIF...DM_CAM:
dvfs_type = DVFS_DEVFREQ;
break;
case DM_GPU:
dvfs_type = DVFS_GPU;
break;
default:
dvfs_type = DVFS_TYPE_END;
dev_err(exynos_dm->dev, "invalid dm_type (%d)\n", dm_type);
break;
}
return dvfs_type;
}
#ifdef CONFIG_OF
static int exynos_dm_parse_dt(struct device_node *np, struct exynos_dm_device *dm)
{
struct device_node *child_np;
int ret = 0;
if (!np)
return -ENODEV;
for_each_child_of_node(np, child_np) {
int index;
const char *available;
#ifdef CONFIG_EXYNOS_ACPM
const char *policy_use;
#endif
if (of_property_read_u32(child_np, "dm-index", &index))
return -ENODEV;
ret = exynos_dm_index_validate(index);
if (ret)
return ret;
if (of_property_read_string(child_np, "available", &available))
return -ENODEV;
if (!strcmp(available, "true")) {
dm->dm_data[index].dm_type = index;
dm->dm_data[index].available = true;
dm->dm_data[index].dvfs_type = exynos_dm_dvfs_type(index);
strncpy(dm->dm_data[index].dm_type_name, dm_type_name[index],
EXYNOS_DM_TYPE_NAME_LEN);
INIT_LIST_HEAD(&dm->dm_data[index].min_clist);
INIT_LIST_HEAD(&dm->dm_data[index].max_clist);
} else {
dm->dm_data[index].available = false;
}
#ifdef CONFIG_EXYNOS_ACPM
if (of_property_read_string(child_np, "policy_use", &policy_use)) {
dev_info(dm->dev, "This doesn't need to send policy to ACPM\n");
} else {
if (!strcmp(policy_use, "true"))
dm->dm_data[index].policy_use = true;
}
if (of_property_read_u32(child_np, "cal_id", &dm->dm_data[index].cal_id))
return -ENODEV;
#endif
}
return ret;
}
#else
static int exynos_dm_parse_dt(struct device_node *np, struct exynos_dm_device *dm)
{
return -ENODEV;
}
#endif
static struct list_head *get_min_constraint_list(struct exynos_dm_data *dm_data)
{
return &dm_data->min_clist;
}
static struct list_head *get_max_constraint_list(struct exynos_dm_data *dm_data)
{
return &dm_data->max_clist;
}
/*
* This function should be called from each DVFS drivers
* before DVFS driver registration to DVFS framework.
* Initialize sequence Step.1
*/
int exynos_dm_data_init(enum exynos_dm_type dm_type,
u32 min_freq, u32 max_freq, u32 cur_freq)
{
int ret = 0;
ret = exynos_dm_index_validate(dm_type);
if (ret)
return ret;
mutex_lock(&exynos_dm->lock);
if (!exynos_dm->dm_data[dm_type].available) {
dev_err(exynos_dm->dev,
"This dm type(%d) is not available\n", dm_type);
ret = -ENODEV;
goto out;
}
exynos_dm->dm_data[dm_type].gov_min_freq = min_freq;
exynos_dm->dm_data[dm_type].policy_min_freq = min_freq;
exynos_dm->dm_data[dm_type].policy_max_freq = max_freq;
exynos_dm->dm_data[dm_type].cur_freq = cur_freq;
if (!exynos_dm->dm_data[dm_type].min_freq)
exynos_dm->dm_data[dm_type].min_freq = min_freq;
if (!exynos_dm->dm_data[dm_type].max_freq)
exynos_dm->dm_data[dm_type].max_freq = max_freq;
out:
mutex_unlock(&exynos_dm->lock);
return ret;
}
/*
* Initialize sequence Step.2
*/
int register_exynos_dm_constraint_table(enum exynos_dm_type dm_type,
struct exynos_dm_constraint *constraint)
{
struct exynos_dm_constraint *sub_constraint;
int i, ret = 0;
ret = exynos_dm_index_validate(dm_type);
if (ret)
return ret;
if (!constraint) {
dev_err(exynos_dm->dev, "constraint is not valid\n");
return -EINVAL;
}
/* check member invalid */
if ((constraint->constraint_type < CONSTRAINT_MIN) ||
(constraint->constraint_type > CONSTRAINT_MAX)) {
dev_err(exynos_dm->dev, "constraint_type is invalid\n");
return -EINVAL;
}
ret = exynos_dm_index_validate(constraint->constraint_dm_type);
if (ret)
return ret;
if (!constraint->freq_table) {
dev_err(exynos_dm->dev, "No frequency table for constraint\n");
return -EINVAL;
}
mutex_lock(&exynos_dm->lock);
strncpy(constraint->dm_type_name,
dm_type_name[constraint->constraint_dm_type],
EXYNOS_DM_TYPE_NAME_LEN);
constraint->min_freq = 0;
constraint->max_freq = UINT_MAX;
if (constraint->constraint_type == CONSTRAINT_MIN)
list_add(&constraint->node, &exynos_dm->dm_data[dm_type].min_clist);
else if (constraint->constraint_type == CONSTRAINT_MAX)
list_add(&constraint->node, &exynos_dm->dm_data[dm_type].max_clist);
/* check guidance and sub constraint table generations */
if (constraint->guidance && (constraint->constraint_type == CONSTRAINT_MIN)) {
sub_constraint = kzalloc(sizeof(struct exynos_dm_constraint), GFP_KERNEL);
if (sub_constraint == NULL) {
dev_err(exynos_dm->dev, "failed to allocate sub constraint\n");
ret = -ENOMEM;
goto err_sub_const;
}
sub_constraint->guidance = true;
sub_constraint->table_length = constraint->table_length;
sub_constraint->constraint_type = CONSTRAINT_MAX;
sub_constraint->constraint_dm_type = dm_type;
strncpy(sub_constraint->dm_type_name,
dm_type_name[sub_constraint->constraint_dm_type],
EXYNOS_DM_TYPE_NAME_LEN);
sub_constraint->min_freq = 0;
sub_constraint->max_freq = UINT_MAX;
sub_constraint->freq_table =
kzalloc(sizeof(struct exynos_dm_freq) * sub_constraint->table_length, GFP_KERNEL);
if (sub_constraint->freq_table == NULL) {
dev_err(exynos_dm->dev, "failed to allocate freq table for sub const\n");
ret = -ENOMEM;
goto err_freq_table;
}
/* generation table */
for (i = 0; i < constraint->table_length; i++) {
sub_constraint->freq_table[i].master_freq =
constraint->freq_table[i].constraint_freq;
sub_constraint->freq_table[i].constraint_freq =
constraint->freq_table[i].master_freq;
}
list_add(&sub_constraint->node,
&exynos_dm->dm_data[constraint->constraint_dm_type].max_clist);
/* linked sub constraint */
constraint->sub_constraint = sub_constraint;
}
mutex_unlock(&exynos_dm->lock);
return 0;
err_freq_table:
kfree(sub_constraint);
err_sub_const:
list_del(&constraint->node);
mutex_unlock(&exynos_dm->lock);
return ret;
}
int unregister_exynos_dm_constraint_table(enum exynos_dm_type dm_type,
struct exynos_dm_constraint *constraint)
{
struct exynos_dm_constraint *sub_constraint;
int ret = 0;
ret = exynos_dm_index_validate(dm_type);
if (ret)
return ret;
if (!constraint) {
dev_err(exynos_dm->dev, "constraint is not valid\n");
return -EINVAL;
}
mutex_lock(&exynos_dm->lock);
if (constraint->sub_constraint) {
sub_constraint = constraint->sub_constraint;
list_del(&sub_constraint->node);
kfree(sub_constraint->freq_table);
kfree(sub_constraint);
}
list_del(&constraint->node);
mutex_unlock(&exynos_dm->lock);
return 0;
}
/*
* This function should be called from each DVFS driver registration function
* before return to corresponding DVFS drvier.
* Initialize sequence Step.3
*/
int register_exynos_dm_freq_scaler(enum exynos_dm_type dm_type,
int (*scaler_func)(enum exynos_dm_type dm_type, u32 target_freq, unsigned int relation))
{
int ret = 0;
ret = exynos_dm_index_validate(dm_type);
if (ret)
return ret;
if (!scaler_func) {
dev_err(exynos_dm->dev, "function is not valid\n");
return -EINVAL;
}
mutex_lock(&exynos_dm->lock);
if (!exynos_dm->dm_data[dm_type].available) {
dev_err(exynos_dm->dev,
"This dm type(%d) is not available\n", dm_type);
ret = -ENODEV;
goto out;
}
if (!exynos_dm->dm_data[dm_type].freq_scaler)
exynos_dm->dm_data[dm_type].freq_scaler = scaler_func;
out:
mutex_unlock(&exynos_dm->lock);
return 0;
}
int unregister_exynos_dm_freq_scaler(enum exynos_dm_type dm_type)
{
int ret = 0;
ret = exynos_dm_index_validate(dm_type);
if (ret)
return ret;
mutex_lock(&exynos_dm->lock);
if (!exynos_dm->dm_data[dm_type].available) {
dev_err(exynos_dm->dev,
"This dm type(%d) is not available\n", dm_type);
ret = -ENODEV;
goto out;
}
if (exynos_dm->dm_data[dm_type].freq_scaler)
exynos_dm->dm_data[dm_type].freq_scaler = NULL;
out:
mutex_unlock(&exynos_dm->lock);
return 0;
}
/*
* Policy Updater
*
* @dm_type: DVFS domain type for updating policy
* @min_freq: Minimum frequency decided by policy
* @max_freq: Maximum frequency decided by policy
*
* In this function, policy_min_freq and policy_max_freq will be changed.
* After that, DVFS Manager will decide min/max freq. of current domain
* and check dependent domains whether update is necessary.
*/
static int dm_data_updater(enum exynos_dm_type dm_type);
static int constraint_checker_min(struct list_head *head, u32 freq);
static int constraint_checker_max(struct list_head *head, u32 freq);
static int constraint_data_updater(enum exynos_dm_type dm_type, int cnt);
static int max_constraint_data_updater(enum exynos_dm_type dm_type, int cnt);
static int scaling_callback(enum dvfs_direction dir, unsigned int relation);
static bool max_flag = false;
#define POLICY_REQ 4
static int __policy_update_call_to_DM(enum exynos_dm_type dm_type, u32 min_freq, u32 max_freq)
{
struct exynos_dm_data *dm;
struct timeval pre, before, after;
#ifdef CONFIG_EXYNOS_ACPM
struct ipc_config config;
unsigned int cmd[4];
int size, ch_num, ret;
#endif
s32 time = 0, pre_time = 0;
exynos_ss_dm((int)dm_type, min_freq, max_freq, pre_time, time);
do_gettimeofday(&pre);
do_gettimeofday(&before);
min_freq = min(min_freq, max_freq);
dm = &exynos_dm->dm_data[dm_type];
if ((dm->policy_min_freq == min_freq) && (dm->policy_max_freq == max_freq))
goto out;
update_policy_min_max_freq(dm, min_freq, max_freq);
/* Check dependent domains */
/*Send policy to FVP*/
#ifdef CONFIG_EXYNOS_ACPM
if (dm->policy_use) {
ret = acpm_ipc_request_channel(exynos_dm->dev->of_node, NULL, &ch_num, &size);
if (ret) {
dev_err(exynos_dm->dev,
"acpm request channel is failed, id:%u, size:%u\n", ch_num, size);
goto out;
}
config.cmd = cmd;
config.response = true;
config.indirection = false;
config.cmd[0] = dm->cal_id;
config.cmd[1] = max_freq;
config.cmd[2] = POLICY_REQ;
ret = acpm_ipc_send_data(ch_num, &config);
if (ret) {
dev_err(exynos_dm->dev, "Failed to send policy data to FVP");
goto out;
}
}
#endif
out:
do_gettimeofday(&after);
pre_time = (before.tv_sec - pre.tv_sec) * USEC_PER_SEC +
(before.tv_usec - pre.tv_usec);
time = (after.tv_sec - before.tv_sec) * USEC_PER_SEC +
(after.tv_usec - before.tv_usec);
exynos_ss_dm((int)dm_type, min_freq, max_freq, pre_time, time);
return 0;
}
static int constraint_checker_min(struct list_head *head, u32 freq)
{
struct exynos_dm_data *dm;
struct exynos_dm_constraint *constraint;
int i;
if (!list_empty(head)) {
list_for_each_entry(constraint, head, node) {
for (i = constraint->table_length - 1; i >= 0; i--) {
if (freq <= constraint->freq_table[i].master_freq) {
constraint->min_freq = constraint->freq_table[i].constraint_freq;
break;
}
}
dm_data_updater(constraint->constraint_dm_type);
dm = &exynos_dm->dm_data[constraint->constraint_dm_type];
constraint_checker_min(get_min_constraint_list(dm), dm->min_freq);
}
}
return 0;
}
static int constraint_checker_max(struct list_head *head, u32 freq)
{
struct exynos_dm_data *dm;
struct exynos_dm_constraint *constraint;
int i;
if (!list_empty(head)) {
list_for_each_entry(constraint, head, node) {
for (i = 0; i < constraint->table_length; i++) {
if (freq >= constraint->freq_table[i].master_freq) {
constraint->max_freq = constraint->freq_table[i].constraint_freq;
break;
}
}
dm_data_updater(constraint->constraint_dm_type);
dm = &exynos_dm->dm_data[constraint->constraint_dm_type];
constraint_checker_max(get_max_constraint_list(dm), dm->max_freq);
}
}
return 0;
}
/*
* DM CALL
*/
static int __DM_CALL(enum exynos_dm_type dm_type, unsigned long *target_freq)
{
struct exynos_dm_data *dm;
int i;
int ret;
unsigned int relation = EXYNOS_DM_RELATION_L;
u32 old_min_freq;
struct timeval pre, before, after;
s32 time = 0, pre_time = 0;
exynos_ss_dm((int)dm_type, *target_freq, 1, pre_time, time);
do_gettimeofday(&pre);
do_gettimeofday(&before);
dm = &exynos_dm->dm_data[dm_type];
old_min_freq = dm->min_freq;
dm->gov_min_freq = (u32)(*target_freq);
if (dm->gov_min_freq > dm->policy_max_freq)
dm->gov_min_freq = dm->policy_max_freq;
for (i = 0; i < DM_TYPE_END; i++)
(&exynos_dm->dm_data[i])->constraint_checked = 0;
if (dm->policy_max_freq < dm->cur_freq)
max_flag = true;
else
max_flag = false;
ret = dm_data_updater(dm_type);
if (ret) {
pr_err("Failed to update DM DATA!\n");
return -EAGAIN;
}
dm->target_freq = (u32)(*target_freq);
if (dm->target_freq < dm->min_freq)
dm->target_freq = dm->min_freq;
if (dm->target_freq >= dm->max_freq) {
dm->target_freq = dm->max_freq;
relation = EXYNOS_DM_RELATION_H;
}
*target_freq = dm->target_freq;
/* Constratin checker should be called to decide target frequency */
constraint_data_updater(dm_type, 1);
max_constraint_data_updater(dm_type, 1);
if (dm->target_freq > dm->cur_freq)
scaling_callback(UP, relation);
else if (dm->target_freq < dm->cur_freq)
scaling_callback(DOWN, relation);
else if (dm->min_freq > old_min_freq)
scaling_callback(UP, relation);
else if (dm->min_freq < old_min_freq)
scaling_callback(DOWN, relation);
/* min/max order clear */
for (i = 0; i <= DM_TYPE_END; i++) {
min_order[i] = DM_EMPTY;
max_order[i] = DM_EMPTY;
}
do_gettimeofday(&after);
pre_time = (before.tv_sec - pre.tv_sec) * USEC_PER_SEC +
(before.tv_usec - pre.tv_usec);
time = (after.tv_sec - before.tv_sec) * USEC_PER_SEC +
(after.tv_usec - before.tv_usec);
exynos_ss_dm((int)dm_type, *target_freq, 3, pre_time, time);
return 0;
}
static int dm_data_updater(enum exynos_dm_type dm_type)
{
struct exynos_dm_data *dm;
struct exynos_dm_constraint *constraint;
struct list_head *constraint_list;
int i;
/* Initial min/max frequency is set to policy min/max frequency */
u32 min_freq;
u32 max_freq;
dm = &exynos_dm->dm_data[dm_type];
min_freq = dm->policy_min_freq;
max_freq = dm->policy_max_freq;
/* Check min/max constraint conditions */
for (i = 0; i < DM_TYPE_END; i++) {
if (!exynos_dm->dm_data[i].available)
continue;
constraint_list = get_min_constraint_list(&exynos_dm->dm_data[i]);
if (list_empty(constraint_list))
continue;
list_for_each_entry(constraint, constraint_list, node) {
if (constraint->constraint_dm_type == dm_type)
min_freq = max(min_freq, constraint->min_freq);
}
}
for (i = 0; i < DM_TYPE_END; i++) {
if (!exynos_dm->dm_data[i].available)
continue;
constraint_list = get_max_constraint_list(&exynos_dm->dm_data[i]);
if (list_empty(constraint_list))
continue;
list_for_each_entry(constraint, constraint_list, node) {
if (constraint->constraint_dm_type == dm_type)
max_freq = min(max_freq, constraint->max_freq);
}
}
min_freq = max(min_freq, dm->gov_min_freq); //MIN freq should be checked with gov_min_freq
update_min_max_freq(dm, min_freq, max_freq);
return 0;
}
int policy_update_call_to_DM(enum exynos_dm_type dm_type, u32 min_freq, u32 max_freq)
{
int ret = 0;
mutex_lock(&exynos_dm->lock);
ret = __policy_update_call_to_DM(dm_type, min_freq, max_freq);
mutex_unlock(&exynos_dm->lock);
return ret;
}
int DM_CALL(enum exynos_dm_type dm_type, unsigned long *target_freq)
{
int ret = 0;
mutex_lock(&exynos_dm->lock);
ret = __DM_CALL(dm_type, target_freq);
mutex_unlock(&exynos_dm->lock);
return ret;
}
int policy_update_with_DM_CALL(enum exynos_dm_type dm_type, u32 min_freq, u32 max_freq, unsigned long *target_freq)
{
int ret = 0;
mutex_lock(&exynos_dm->lock);
__policy_update_call_to_DM(dm_type, min_freq, max_freq);
ret = __DM_CALL(dm_type, target_freq);
mutex_unlock(&exynos_dm->lock);
return ret;
}
static int constraint_data_updater(enum exynos_dm_type dm_type, int cnt)
{
struct exynos_dm_data *dm;
struct exynos_dm_constraint *constraint;
struct list_head *constraint_list;
dm = &exynos_dm->dm_data[dm_type];
/* Check dependent domains */
constraint_checker_min(get_min_constraint_list(dm), dm->min_freq);
if (!dm->constraint_checked)
dm->constraint_checked += cnt;
min_order[dm->constraint_checked] = dm_type;
constraint_list = get_min_constraint_list(dm);
if (list_empty(constraint_list))
return 0;
min_order[0] = 0;
list_for_each_entry(constraint, constraint_list, node) {
dm = &exynos_dm->dm_data[constraint->constraint_dm_type];
dm_data_updater(dm->dm_type);
dm->target_freq = dm->min_freq;
if (dm->target_freq >= dm->max_freq)
dm->target_freq = dm->max_freq;
constraint_data_updater(dm->dm_type, cnt + 1);
}
return 0;
}
static int max_constraint_data_updater(enum exynos_dm_type dm_type, int cnt)
{
struct exynos_dm_data *dm;
struct exynos_dm_constraint *constraint;
struct list_head *constraint_list;
dm = &exynos_dm->dm_data[dm_type];
/* Check dependent domains */
constraint_checker_max(get_max_constraint_list(dm), dm->max_freq);
if (!dm->constraint_checked)
dm->constraint_checked += cnt;
max_order[dm->constraint_checked] = dm_type;
constraint_list = get_max_constraint_list(dm);
if (list_empty(constraint_list))
return 0;
max_order[0] = 0;
list_for_each_entry(constraint, constraint_list, node) {
dm = &exynos_dm->dm_data[constraint->constraint_dm_type];
dm_data_updater(dm->dm_type);
dm->target_freq = dm->min_freq;
if (dm->target_freq >= dm->max_freq)
dm->target_freq = dm->max_freq;
max_constraint_data_updater(dm->dm_type, cnt + 1);
}
return 0;
}
/*
* Scaling Callback
* Call callback function in each DVFS drivers to scaling frequency
*/
static int scaling_callback(enum dvfs_direction dir, unsigned int relation)
{
struct exynos_dm_data *dm;
int i;
switch (dir) {
case DOWN:
if (min_order[0] == 0 && max_flag == false) {
for (i = 1; i <= DM_TYPE_END; i++) {
if (min_order[i] == DM_EMPTY)
continue;
dm = &exynos_dm->dm_data[min_order[i]];
if (dm->constraint_checked) {
if (dm->freq_scaler) {
dm->freq_scaler(dm->dm_type, dm->target_freq, relation);
dm->cur_freq = dm->target_freq;
}
dm->constraint_checked = 0;
}
}
} else if (max_order[0] == 0 && max_flag == true) {
for (i = DM_TYPE_END; i > 0; i--) {
if (max_order[i] == DM_EMPTY)
continue;
dm = &exynos_dm->dm_data[max_order[i]];
if (dm->constraint_checked) {
if (dm->freq_scaler) {
dm->freq_scaler(dm->dm_type, dm->target_freq, relation);
dm->cur_freq = dm->target_freq;
}
dm->constraint_checked = 0;
}
}
}
break;
case UP:
if (min_order[0] == 0) {
for (i = DM_TYPE_END; i > 0; i--) {
if (min_order[i] == DM_EMPTY)
continue;
dm = &exynos_dm->dm_data[min_order[i]];
if (dm->constraint_checked) {
if (dm->freq_scaler) {
dm->freq_scaler(dm->dm_type, dm->target_freq, relation);
dm->cur_freq = dm->target_freq;
}
dm->constraint_checked = 0;
}
}
} else if (max_order[0] == 0) {
for (i = 1; i <= DM_TYPE_END; i++) {
if (max_order[i] == DM_EMPTY)
continue;
dm = &exynos_dm->dm_data[max_order[i]];
if (dm->constraint_checked) {
if (dm->freq_scaler) {
dm->freq_scaler(dm->dm_type, dm->target_freq, relation);
dm->cur_freq = dm->target_freq;
}
dm->constraint_checked = 0;
}
}
}
break;
default:
break;
}
for (i = 1; i <= DM_TYPE_END; i++) {
if (min_order[i] == DM_EMPTY)
continue;
dm = &exynos_dm->dm_data[min_order[i]];
if (dm->constraint_checked) {
if (dm->freq_scaler) {
dm->freq_scaler(dm->dm_type, dm->target_freq, relation);
dm->cur_freq = dm->target_freq;
}
dm->constraint_checked = 0;
}
}
max_flag = false;
return 0;
}
static void get_governor_min_freq(struct exynos_dm_data *dm_data, u32 *gov_min_freq)
{
*gov_min_freq = dm_data->gov_min_freq;
}
static void get_min_max_freq(struct exynos_dm_data *dm_data, u32 *min_freq, u32 *max_freq)
{
*min_freq = dm_data->min_freq;
*max_freq = dm_data->max_freq;
}
static void update_min_max_freq(struct exynos_dm_data *dm_data, u32 min_freq, u32 max_freq)
{
dm_data->min_freq = min_freq;
dm_data->max_freq = max_freq;
}
static void get_policy_min_max_freq(struct exynos_dm_data *dm_data, u32 *min_freq, u32 *max_freq)
{
*min_freq = dm_data->policy_min_freq;
*max_freq = dm_data->policy_max_freq;
}
static void update_policy_min_max_freq(struct exynos_dm_data *dm_data, u32 min_freq, u32 max_freq)
{
dm_data->policy_min_freq = min_freq;
dm_data->policy_max_freq = max_freq;
}
static void get_current_freq(struct exynos_dm_data *dm_data, u32 *cur_freq)
{
*cur_freq = dm_data->cur_freq;
}
static void get_target_freq(struct exynos_dm_data *dm_data, u32 *target_freq)
{
*target_freq = dm_data->target_freq;
}
static int exynos_dm_suspend(struct device *dev)
{
/* Suspend callback function might be registered if necessary */
return 0;
}
static int exynos_dm_resume(struct device *dev)
{
/* Resume callback function might be registered if necessary */
return 0;
}
static int exynos_dm_probe(struct platform_device *pdev)
{
int ret = 0;
struct exynos_dm_device *dm;
dm = kzalloc(sizeof(struct exynos_dm_device), GFP_KERNEL);
if (dm == NULL) {
dev_err(&pdev->dev, "failed to allocate DVFS Manager device\n");
ret = -ENOMEM;
goto err_device;
}
dm->dev = &pdev->dev;
mutex_init(&dm->lock);
/* parsing devfreq dts data for exynos-dvfs-manager */
ret = exynos_dm_parse_dt(dm->dev->of_node, dm);
if (ret) {
dev_err(dm->dev, "failed to parse private data\n");
goto err_parse_dt;
}
print_available_dm_data(dm);
ret = sysfs_create_group(&dm->dev->kobj, &exynos_dm_attr_group);
if (ret)
dev_warn(dm->dev, "failed create sysfs for DVFS Manager\n");
exynos_dm = dm;
platform_set_drvdata(pdev, dm);
return 0;
err_parse_dt:
mutex_destroy(&dm->lock);
kfree(dm);
err_device:
return ret;
}
static int exynos_dm_remove(struct platform_device *pdev)
{
struct exynos_dm_device *dm = platform_get_drvdata(pdev);
sysfs_remove_group(&dm->dev->kobj, &exynos_dm_attr_group);
mutex_destroy(&dm->lock);
kfree(dm);
return 0;
}
static struct platform_device_id exynos_dm_driver_ids[] = {
{
.name = EXYNOS_DM_MODULE_NAME,
},
{},
};
MODULE_DEVICE_TABLE(platform, exynos_dm_driver_ids);
static const struct of_device_id exynos_dm_match[] = {
{
.compatible = "samsung,exynos-dvfs-manager",
},
{},
};
MODULE_DEVICE_TABLE(of, exynos_dm_match);
static const struct dev_pm_ops exynos_dm_pm_ops = {
.suspend = exynos_dm_suspend,
.resume = exynos_dm_resume,
};
static struct platform_driver exynos_dm_driver = {
.probe = exynos_dm_probe,
.remove = exynos_dm_remove,
.id_table = exynos_dm_driver_ids,
.driver = {
.name = EXYNOS_DM_MODULE_NAME,
.owner = THIS_MODULE,
.pm = &exynos_dm_pm_ops,
.of_match_table = exynos_dm_match,
},
};
static int __init exynos_dm_init(void)
{
return platform_driver_register(&exynos_dm_driver);
}
subsys_initcall(exynos_dm_init);
static void __exit exynos_dm_exit(void)
{
platform_driver_unregister(&exynos_dm_driver);
}
module_exit(exynos_dm_exit);
MODULE_AUTHOR("Taekki Kim <taekki.kim@samsung.com>");
MODULE_AUTHOR("Eunok Jo <eunok25.jo@samsung.com>");
MODULE_DESCRIPTION("Samsung EXYNOS SoC series DVFS Manager");
MODULE_LICENSE("GPL");