[COMMON] exynos: DVFS Manager driver code
authorEunok Jo <eunok25.jo@samsung.com>
Wed, 15 Jun 2016 08:37:28 +0000 (17:37 +0900)
committerChungwoo Park <cww.park@samsung.com>
Mon, 21 May 2018 08:22:33 +0000 (17:22 +0900)
DVFS Manager is for checking and controlling constraint
between two or more different frequency domains.

DVFS Manager checks requests from each frequency domains
and decide available frequency in terms of system stability.

Change-Id: I8fd587dd8738a9ee7ab6c23134e18e0e201825f7
Signed-off-by: Eunok Jo <eunok25.jo@samsung.com>
drivers/soc/samsung/Kconfig
drivers/soc/samsung/Makefile
drivers/soc/samsung/exynos-dm.c [new file with mode: 0644]
include/soc/samsung/exynos-dm.h [new file with mode: 0644]

index 6c3fe34f760c8ab11edeb1d3c6932f1e56f080f7..0893705834a221268119473009141b757edb1131 100644 (file)
@@ -96,5 +96,12 @@ config EXYNOS_PM_DOMAINS
        depends on PM_GENERIC_DOMAINS || COMPILE_TEST
 
 source "drivers/soc/samsung/debug/Kconfig"
+config EXYNOS_DVFS_MANAGER
+       bool "Exynos DVFS Manager"
+       default n
+       depends on ARCH_EXYNOS
+       help
+         Enable DVFS Manager for Exynos SoC.
+         This module controls constraint between each DVFS domains.
 
 endif
index 226f95e707ac426b98441f8b2f6c0f77d184e086..e77f64c8ff2b49bb8d4b48ad9e9fe425b17b3465 100644 (file)
@@ -27,3 +27,5 @@ obj-$(CONFIG_EXYNOS_REBOOT)     += exynos-reboot.o
 
 # DEBUG
 obj-$(CONFIG_EXYNOS_DEBUG)      += debug/
+# DVFS
+obj-$(CONFIG_EXYNOS_DVFS_MANAGER)      += exynos-dm.o
diff --git a/drivers/soc/samsung/exynos-dm.c b/drivers/soc/samsung/exynos-dm.c
new file mode 100644 (file)
index 0000000..46d5986
--- /dev/null
@@ -0,0 +1,1010 @@
+/* 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 <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);
+
+static struct exynos_dm_device *exynos_dm;
+
+/*
+ * 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 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"); \
+       }                                                                               \
+                                                                                       \
+       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 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);
+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);
+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);
+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);
+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,
+       &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,
+       &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;
+
+               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;
+               }
+       }
+
+       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 scaling_callback(enum dvfs_direction dir, unsigned int relation);
+
+int policy_update_call_to_DM(enum exynos_dm_type dm_type, u32 min_freq, u32 max_freq)
+{
+       struct exynos_dm_data *dm;
+
+       mutex_lock(&exynos_dm->lock);
+
+       dm = &exynos_dm->dm_data[dm_type];
+       update_policy_min_max_freq(dm, min_freq, max_freq);
+
+       /* Check dependent domains */
+       constraint_checker_min(get_min_constraint_list(dm), min_freq);
+       constraint_checker_max(get_max_constraint_list(dm), max_freq);
+
+       mutex_unlock(&exynos_dm->lock);
+
+       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
+ */
+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;
+
+       mutex_lock(&exynos_dm->lock);
+
+       dm = &exynos_dm->dm_data[dm_type];
+       dm->gov_min_freq = *target_freq;
+
+       for (i = 0; i < DM_TYPE_END; i++)
+               (&exynos_dm->dm_data[i])->constraint_checked = 0;
+
+       ret = dm_data_updater(dm_type);
+       if (ret) {
+               pr_err("Failed to update DM DATA!\n");
+               return -EAGAIN;
+       }
+
+       if (*target_freq >= dm->min_freq && *target_freq < dm->max_freq)
+               dm->target_freq = *target_freq;
+       if (*target_freq < dm->min_freq)
+               dm->target_freq = dm->min_freq;
+       if (*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);
+
+       if (dm->target_freq > dm->cur_freq)
+               scaling_callback(UP, relation);
+       else if (dm->target_freq < dm->cur_freq)
+               scaling_callback(DOWN, relation);
+
+       mutex_unlock(&exynos_dm->lock);
+
+       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;
+}
+
+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->target_freq);
+
+       dm->constraint_checked += cnt;
+
+       constraint_list = get_min_constraint_list(dm);
+       if (list_empty(constraint_list))
+               return 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;
+}
+
+/*
+ * 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 cnt = 0;
+       int i, j;
+
+       switch (dir) {
+       case DOWN:
+               for (j = 0; j < DM_TYPE_END; j++) {
+                       cnt = j;
+                       if ((&exynos_dm->dm_data[cnt])->constraint_checked) {
+                               for (i = 0; i < DM_TYPE_END; i++) {
+                                       if ((&exynos_dm->dm_data[i])->constraint_checked &&
+                                               ((&exynos_dm->dm_data[cnt])->constraint_checked
+                                                       > (&exynos_dm->dm_data[i])->constraint_checked))
+                                               cnt = i;
+                               }
+                               dm = &exynos_dm->dm_data[cnt];
+                               if (dm->freq_scaler) {
+                                       dm->freq_scaler(cnt, dm->target_freq, relation);
+                                       dm->cur_freq = dm->target_freq;
+                               }
+                               (&exynos_dm->dm_data[cnt])->constraint_checked = 0;
+                       }
+               }
+               break;
+       case UP:
+               for (j = 0; j < DM_TYPE_END; j++) {
+                       cnt = j;
+                       if ((&exynos_dm->dm_data[cnt])->constraint_checked) {
+                               for (i = 0; i < DM_TYPE_END; i++) {
+                                       if ((&exynos_dm->dm_data[i])->constraint_checked &&
+                                               ((&exynos_dm->dm_data[cnt])->constraint_checked
+                                                       < (&exynos_dm->dm_data[i])->constraint_checked))
+                                               cnt = i;
+                               }
+                               dm = &exynos_dm->dm_data[cnt];
+                               if (dm->freq_scaler) {
+                                       dm->freq_scaler(cnt, dm->target_freq, relation);
+                                       dm->cur_freq = dm->target_freq;
+                               }
+                               (&exynos_dm->dm_data[cnt])->constraint_checked = 0;
+                       }
+               }
+               break;
+       default:
+               break;
+       }
+
+       for (j = 0; j < DM_TYPE_END; j++)
+               if ((&exynos_dm->dm_data[j])->constraint_checked) {
+                       dm = &exynos_dm->dm_data[j];
+                       if (dm->freq_scaler) {
+                               dm->freq_scaler(j, dm->target_freq, relation);
+                               dm->cur_freq = dm->target_freq;
+                       }
+                       (&exynos_dm->dm_data[j])->constraint_checked = 0;
+               }
+
+       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");
diff --git a/include/soc/samsung/exynos-dm.h b/include/soc/samsung/exynos-dm.h
new file mode 100644 (file)
index 0000000..70f6c33
--- /dev/null
@@ -0,0 +1,170 @@
+/* linux/include/soc/samsung/exynos-dm.h
+ *
+ * Copyright (C) 2016 Samsung Electronics Co., Ltd.
+ *             http://www.samsung.com
+ *
+ * EXYNOS5 - Header file for exynos DVFS Manager support
+ *
+ * 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.
+ */
+
+#ifndef __EXYNOS_DM_H
+#define __EXYNOS_DM_H
+
+#define EXYNOS_DM_MODULE_NAME          "exynos-dm"
+#define EXYNOS_DM_TYPE_NAME_LEN                16
+
+#define EXYNOS_DM_RELATION_L           0
+#define EXYNOS_DM_RELATION_H           1
+
+enum exynos_dm_type {
+       DM_CPU_CL0 = 0,
+       DM_CPU_CL1,
+       DM_MIF,
+       DM_INT,
+       DM_INTCAM,
+       DM_DISP,
+       DM_CAM,
+       DM_GPU,
+       DM_TYPE_END
+};
+
+static const char dm_type_name[DM_TYPE_END][EXYNOS_DM_TYPE_NAME_LEN] = {
+       "dm_cpu_cl0",
+       "dm_cpu_cl1",
+       "dm_mif",
+       "dm_int",
+       "dm_intcam",
+       "dm_disp",
+       "dm_cam",
+       "dm_gpu",
+};
+
+enum exynos_dvfs_type {
+       DVFS_CPUFREQ = 0,
+       DVFS_DEVFREQ,
+       DVFS_GPU,
+       DVFS_TYPE_END
+};
+
+enum exynos_constraint_type {
+       CONSTRAINT_MIN = 0,
+       CONSTRAINT_MAX,
+       CONSTRAINT_END
+};
+
+enum dvfs_direction {
+       DOWN = 0,
+       UP,
+       DIRECTION_END
+};
+
+struct exynos_dm_freq {
+       u32                             master_freq;
+       u32                             constraint_freq;
+};
+
+struct exynos_dm_constraint {
+       struct list_head                node;
+
+       bool                            guidance;               /* check constraint table by hw guide */
+       u32                             table_length;
+
+       enum exynos_constraint_type     constraint_type;
+       enum exynos_dm_type             constraint_dm_type;
+       char                            dm_type_name[EXYNOS_DM_TYPE_NAME_LEN];
+       struct exynos_dm_freq           *freq_table;
+       u32                             min_freq;
+       u32                             max_freq;
+
+       struct exynos_dm_constraint     *sub_constraint;
+};
+
+struct exynos_dm_data {
+       bool                            available;              /* use for DVFS domain available */
+
+       enum exynos_dm_type             dm_type;
+       enum exynos_dvfs_type           dvfs_type;
+       char                            dm_type_name[EXYNOS_DM_TYPE_NAME_LEN];
+
+       u32                             min_freq;
+       u32                             max_freq;
+       u32                             cur_freq;
+       u32                             target_freq;
+
+       u32                             gov_min_freq;
+
+       u32                             policy_min_freq;
+       u32                             policy_max_freq;
+
+       int (*freq_scaler)              (enum exynos_dm_type dm_type, u32 target_freq, unsigned int relation);
+
+       struct list_head                min_clist;
+       struct list_head                max_clist;
+       u32                             constraint_checked;
+};
+
+struct exynos_dm_device {
+       struct device                   *dev;
+       struct mutex                    lock;
+       struct exynos_dm_data           dm_data[DM_TYPE_END];
+};
+
+/* External Function call */
+#if defined(CONFIG_EXYNOS_DVFS_MANAGER)
+int exynos_dm_data_init(enum exynos_dm_type dm_type,
+                       u32 min_freq, u32 max_freq, u32 cur_freq);
+int register_exynos_dm_constraint_table(enum exynos_dm_type dm_type,
+                               struct exynos_dm_constraint *constraint);
+int unregister_exynos_dm_constraint_table(enum exynos_dm_type dm_type,
+                               struct exynos_dm_constraint *constraint);
+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 unregister_exynos_dm_freq_scaler(enum exynos_dm_type dm_type);
+int policy_update_call_to_DM(enum exynos_dm_type dm_type, u32 min_freq, u32 max_freq);
+int DM_CALL(enum exynos_dm_type dm_type, unsigned long *target_freq);
+#else
+static inline
+int exynos_dm_data_init(enum exynos_dm_type dm_type,
+                       u32 min_freq, u32 max_freq, u32 cur_freq)
+{
+       return 0;
+}
+static inline
+int register_exynos_dm_constraint_table(enum exynos_dm_type dm_type,
+                               struct exynos_dm_constraint *constraint)
+{
+       return 0;
+}
+static inline
+int unregister_exynos_dm_constraint_table(enum exynos_dm_type dm_type,
+                               struct exynos_dm_constraint *constraint)
+{
+       return 0;
+}
+static inline
+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))
+{
+       return 0;
+}
+static inline
+int unregister_exynos_dm_freq_scaler(enum exynos_dm_type dm_type)
+{
+       return 0;
+}
+static inline
+int policy_update_call_to_DM(enum exynos_dm_type dm_type, u32 min_freq, u32 max_freq)
+{
+       return 0;
+}
+static inline
+int DM_CALL(enum exynos_dm_type dm_type, unsigned long *target_freq)
+{
+       return 0;
+}
+#endif
+
+#endif /* __EXYNOS_DM_H */