From 2212c3f05d367c26472b9ff146d7305f3754e759 Mon Sep 17 00:00:00 2001 From: Chungwoo Park Date: Mon, 14 May 2018 18:26:03 +0900 Subject: [PATCH] devfreq: Bring up to exynos devfreq. Change-Id: I5f8e4a0b203e4046fd3378111faf24cd400caa50 Signed-off-by: Chungwoo Park --- drivers/devfreq/Kconfig | 36 + drivers/devfreq/Makefile | 4 + drivers/devfreq/exynos/Makefile | 3 + drivers/devfreq/exynos/exynos-devfreq.c | 1596 +++++++++++++++++ drivers/devfreq/exynos/exynos_ppmu.c | 111 ++ drivers/devfreq/exynos/exynos_ppmu.h | 32 + drivers/devfreq/governor_simpleexynos.c | 261 +++ drivers/devfreq/governor_simpleinteractive.c | 270 +++ drivers/devfreq/governor_simpleusage.c | 194 ++ .../soc/samsung/exynos9610-devfreq.h | 33 + include/linux/devfreq.h | 42 +- include/soc/samsung/exynos-devfreq.h | 112 ++ 12 files changed, 2692 insertions(+), 2 deletions(-) create mode 100644 drivers/devfreq/exynos/Makefile create mode 100644 drivers/devfreq/exynos/exynos-devfreq.c create mode 100644 drivers/devfreq/exynos/exynos_ppmu.c create mode 100644 drivers/devfreq/exynos/exynos_ppmu.h create mode 100644 drivers/devfreq/governor_simpleexynos.c create mode 100644 drivers/devfreq/governor_simpleinteractive.c create mode 100644 drivers/devfreq/governor_simpleusage.c create mode 100644 include/dt-bindings/soc/samsung/exynos9610-devfreq.h create mode 100644 include/soc/samsung/exynos-devfreq.h diff --git a/drivers/devfreq/Kconfig b/drivers/devfreq/Kconfig index 6a172d338f6d..0df5d5609fea 100644 --- a/drivers/devfreq/Kconfig +++ b/drivers/devfreq/Kconfig @@ -39,6 +39,21 @@ config DEVFREQ_GOV_SIMPLE_ONDEMAND Simple-Ondemand should be able to provide busy/total counter values that imply the usage rate. A device may provide tuned values to the governor with data field at devfreq_add_device(). +config DEVFREQ_GOV_SIMPLE_USAGE + tristate "Simple Usage" + help + Chooses frequency based on the percentage of target device. + +config DEVFREQ_GOV_SIMPLE_EXYNOS + tristate "Simple Exynos" + help + Chooses frequency based on the threshold of target device. + +config DEVFREQ_GOV_SIMPLE_INTERACTIVE + tristate "Simple Interactive" + help + Chooses frequency based on the requested PM QoS from target device. + And This governor uses timer when change frequency. config DEVFREQ_GOV_PERFORMANCE tristate "Performance" @@ -75,6 +90,18 @@ config DEVFREQ_GOV_PASSIVE comment "DEVFREQ Drivers" +config ARM_EXYNOS_DEVFREQ + bool "ARM Exynos Memory Bus DEVFREQ Common driver" + depends on ARCH_EXYNOS + select ARCH_HAS_OPP + select PM_OPP + help + This adds the DEVFREQ driver for Exynos series common bus. + +config ARM_EXYNOS_DEVFREQ_DEBUG + bool "ARM Exynos DEVFREQ sysfs for Debug" + depends on ARM_EXYNOS_DEVFREQ + config ARM_EXYNOS_BUS_DEVFREQ tristate "ARM EXYNOS Generic Memory Bus DEVFREQ Driver" depends on ARCH_EXYNOS || COMPILE_TEST @@ -115,4 +142,13 @@ config ARM_RK3399_DMC_DEVFREQ source "drivers/devfreq/event/Kconfig" +config ARM_EXYNOS9610_BUS_DEVFREQ + bool "ARM Exynos9610 Memory Bus DEVFREQ Driver" + default y + depends on ARM_EXYNOS_DEVFREQ + depends on SOC_EXYNOS9610 + select DEVFREQ_GOV_SIMPLE_INTERACTIVE + help + This adds the DEVFREQ driver for Exynos9610 memory and int. + endif # PM_DEVFREQ diff --git a/drivers/devfreq/Makefile b/drivers/devfreq/Makefile index 32b8d4d3f12c..2d55dab951e4 100644 --- a/drivers/devfreq/Makefile +++ b/drivers/devfreq/Makefile @@ -2,6 +2,9 @@ obj-$(CONFIG_PM_DEVFREQ) += devfreq.o obj-$(CONFIG_PM_DEVFREQ_EVENT) += devfreq-event.o obj-$(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND) += governor_simpleondemand.o +obj-$(CONFIG_DEVFREQ_GOV_SIMPLE_EXYNOS) += governor_simpleexynos.o +obj-$(CONFIG_DEVFREQ_GOV_SIMPLE_INTERACTIVE) += governor_simpleinteractive.o +obj-$(CONFIG_DEVFREQ_GOV_SIMPLE_USAGE) += governor_simpleusage.o obj-$(CONFIG_DEVFREQ_GOV_PERFORMANCE) += governor_performance.o obj-$(CONFIG_DEVFREQ_GOV_POWERSAVE) += governor_powersave.o obj-$(CONFIG_DEVFREQ_GOV_USERSPACE) += governor_userspace.o @@ -14,3 +17,4 @@ obj-$(CONFIG_ARM_TEGRA_DEVFREQ) += tegra-devfreq.o # DEVFREQ Event Drivers obj-$(CONFIG_PM_DEVFREQ_EVENT) += event/ +obj-$(CONFIG_ARM_EXYNOS_DEVFREQ) += exynos/ diff --git a/drivers/devfreq/exynos/Makefile b/drivers/devfreq/exynos/Makefile new file mode 100644 index 000000000000..d9df799debb2 --- /dev/null +++ b/drivers/devfreq/exynos/Makefile @@ -0,0 +1,3 @@ +# Exynos DEVFREQ Drivers +obj-$(CONFIG_ARM_EXYNOS_DEVFREQ) += exynos-devfreq.o +obj-$(CONFIG_EXYNOS_WD_DVFS) += exynos_ppmu.o diff --git a/drivers/devfreq/exynos/exynos-devfreq.c b/drivers/devfreq/exynos/exynos-devfreq.c new file mode 100644 index 000000000000..ea4f05fcddcf --- /dev/null +++ b/drivers/devfreq/exynos/exynos-devfreq.c @@ -0,0 +1,1596 @@ +/* linux/drivers/devfreq/exynos-devfreq.c + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Samsung EXYNOS SoC series devfreq common 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../soc/samsung/cal-if/acpm_dvfs.h" +#include + +#include +#include +#ifdef CONFIG_EXYNOS_DVFS_MANAGER +#include +#endif +#ifdef CONFIG_EXYNOS_ACPM +#include "../../soc/samsung/acpm/acpm.h" +#include "../../soc/samsung/acpm/acpm_ipc.h" +#endif + +#include "../governor.h" + +static struct exynos_devfreq_data **devfreq_data; + +static u32 freq_array[6]; +static u32 boot_array[2]; + +#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 exynos_constraint_parse(struct exynos_devfreq_data *data, + unsigned int min_freq, unsigned int max_freq) +{ + struct device_node *np, *child; + u32 num_child, constraint_dm_type, constraint_type; + const char *devfreq_domain_name; + int i = 0, j, const_flag = 1; + void *min_block, *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 + np = of_get_child_by_name(data->dev->of_node, "skew"); + if (!np) + return 0; + num_child = of_get_child_count(np); +#ifdef CONFIG_EXYNOS_DVFS_MANAGER + data->nr_constraint = num_child; + data->constraint = kzalloc(sizeof(struct exynos_dm_constraint *) * num_child, GFP_KERNEL); +#endif + if (of_property_read_string(data->dev->of_node, "devfreq_domain_name", &devfreq_domain_name)) + return -ENODEV; + + dvfs_block = ect_get_block(BLOCK_DVFS); + if (dvfs_block == NULL) + return -ENODEV; + + dvfs_domain = ect_dvfs_get_domain(dvfs_block, (char *)devfreq_domain_name); + 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, (char *)devfreq_domain_name); + if (ect_domain == NULL) { + dev_info(data->dev, "There is not a domain in min block\n"); + const_flag = 0; + } + + for_each_available_child_of_node(np, child) { + int use_level = 0; + + if (of_property_read_u32(child, "constraint_dm_type", &constraint_dm_type)) + return -ENODEV; + if (of_property_read_u32(child, "constraint_type", &constraint_type)) + return -ENODEV; +#ifdef CONFIG_EXYNOS_DVFS_MANAGER + if (const_flag) { + data->constraint[i] = + kzalloc(sizeof(struct exynos_dm_constraint), GFP_KERNEL); + if (data->constraint[i] == 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[i]); + return -ENOMEM; + } + + data->constraint[i]->guidance = true; + data->constraint[i]->constraint_type = constraint_type; + data->constraint[i]->constraint_dm_type = constraint_dm_type; + data->constraint[i]->table_length = ect_domain->num_of_level; + data->constraint[i]->freq_table = const_table; + } +#endif + for (j = 0; j < dvfs_domain->num_of_level; j++) { + if (data->opp_list[j].freq > max_freq || + data->opp_list[j].freq < min_freq) + continue; + +#ifdef CONFIG_EXYNOS_DVFS_MANAGER + if (const_flag) { + const_table[use_level].master_freq = data->opp_list[j].freq; + const_table[use_level].constraint_freq + = ect_find_constraint_freq(ect_domain, data->opp_list[j].freq); + } +#endif + use_level++; + } + i++; + } + return 0; +} + +static int exynos_devfreq_update_fvp(struct exynos_devfreq_data *data, u32 min_freq, u32 max_freq) +{ + int ret, ch_num, size, i, use_level = 0; + u32 cmd[4]; + struct ipc_config config; + int nr_constraint = 0; +#ifdef CONFIG_EXYNOS_DVFS_MANAGER + int j; + struct exynos_dm_constraint *constraint; + + nr_constraint = data->nr_constraint; +#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; + + /* constraint info update */ + if (nr_constraint == 0) { + for (i = 0; i < data->max_state; 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; + ret = acpm_ipc_send_data(ch_num, &config); + if (ret) { + dev_err(data->dev, "make constraint table is failed"); + return -EINVAL; + } + use_level++; + } + } +#ifdef CONFIG_EXYNOS_DVFS_MANAGER + else { + for (i = 0; i < data->nr_constraint; i++) { + constraint = data->constraint[i]; + for (j = 0; j < data->max_state; j++) { + if (data->opp_list[j].freq > max_freq || + data->opp_list[j].freq < min_freq) + continue; + + config.cmd[0] = use_level; + config.cmd[1] = data->opp_list[j].freq; + config.cmd[2] = DATA_INIT; + config.cmd[3] = constraint->freq_table[use_level].constraint_freq; + 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 */ + config.cmd[0] = use_level; + config.cmd[1] = (unsigned int)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; + } + } +#endif + + return 0; +} + +static int exynos_devfreq_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 exynos_devfreq_get_freq(struct device *dev, u32 *cur_freq, + struct clk *clk, struct exynos_devfreq_data *data) +{ + if (data->pm_domain) { + if (!exynos_pd_status(data->pm_domain)) { + dev_err(dev, "power domain %s is offed\n", data->pm_domain->name); + *cur_freq = 0; + return -EINVAL; + } + } + + *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 exynos_devfreq_set_freq(struct device *dev, u32 new_freq, + struct clk *clk, struct exynos_devfreq_data *data) +{ +#ifdef CONFIG_EXYNOS_BTS + if (data->bts_update) { + if (data->new_freq < data->old_freq) + bts_update_scen(BS_MIF_CHANGE, data->new_freq); + } +#endif + if (data->pm_domain) { + if (!exynos_pd_status(data->pm_domain)) { + dev_err(dev, "power domain %s is offed\n", data->pm_domain->name); + return -EINVAL; + } + } + + 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; + } + +#ifdef CONFIG_EXYNOS_BTS + if (data->bts_update) { + if (data->new_freq > data->old_freq) + bts_update_scen(BS_MIF_CHANGE, data->new_freq); + } +#endif + + return 0; +} + +static int exynos_devfreq_init_freq_table(struct exynos_devfreq_data *data) +{ + u32 max_freq, min_freq; + unsigned long tmp_max, tmp_min; + struct dev_pm_opp *target_opp; + u32 flags = 0; + int i, ret; + + 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) { + 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)) { + dev_err(data->dev, "not found valid OPP for max_freq\n"); + return PTR_ERR(target_opp); + } + + data->max_freq = (u32)dev_pm_opp_get_freq(target_opp); + dev_pm_opp_put(target_opp); + } + + /* 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) { + 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)) { + dev_err(data->dev, "not found valid OPP for min_freq\n"); + return PTR_ERR(target_opp); + } + + data->min_freq = (u32)dev_pm_opp_get_freq(target_opp); + dev_pm_opp_put(target_opp); + } + + dev_info(data->dev, "min_freq: %uKhz, max_freq: %uKhz\n", + data->min_freq, data->max_freq); + + for (i = 0; i < data->max_state; i++) { + if (data->opp_list[i].freq > data->max_freq || + data->opp_list[i].freq < data->min_freq) + dev_pm_opp_disable(data->dev, (unsigned long)data->opp_list[i].freq); + } + + data->devfreq_profile.initial_freq = cal_dfs_get_boot_freq(data->dfs_id); + data->devfreq_profile.suspend_freq = cal_dfs_get_resume_freq(data->dfs_id); + + ret = exynos_constraint_parse(data, min_freq, max_freq); + if (ret) { + dev_err(data->dev, "failed to parse constraint table\n"); + return -EINVAL; + } + + if (data->update_fvp) + exynos_devfreq_update_fvp(data, min_freq, max_freq); + + if (data->use_acpm) { + 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; +} + +#ifdef CONFIG_ARM_EXYNOS_DEVFREQ_DEBUG +static ssize_t show_exynos_devfreq_info(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + ssize_t count = 0; + int i; + + count = snprintf(buf, PAGE_SIZE, "[Exynos DEVFREQ Data]\n" + "devfreq dev name : %20s\n" + "devfreq type : %20d\n" + "Exynos SS flag : %20u\n", + dev_name(data->dev), data->devfreq_type, data->ess_flag); + + count += snprintf(buf + count, PAGE_SIZE, "\n\n" + "OPP list length : %20u\n", data->max_state); + count += snprintf(buf + count, PAGE_SIZE, "freq opp table\n"); + count += snprintf(buf + count, PAGE_SIZE, "\t idx freq volt\n"); + + for (i = 0; i < data->max_state; i++) + count += snprintf(buf + count, PAGE_SIZE, "\t%5u %10u %10u\n", + data->opp_list[i].idx, data->opp_list[i].freq, + data->opp_list[i].volt); + + count += snprintf(buf + count, PAGE_SIZE, + "default_qos : %20u\n" "initial_freq : %20lu\n" + "min_freq : %20u\n" "max_freq : %20u\n" + "boot_timeout(s) : %20u\n" "max_state : %20u\n", + data->default_qos, data->devfreq_profile.initial_freq, + data->min_freq, data->max_freq, data->boot_qos_timeout, data->max_state); + + count += snprintf(buf + count, PAGE_SIZE, "\n\n"); + count += snprintf(buf + count, PAGE_SIZE, + "governor_name : %20s\n", + data->governor_name); + return count; +} + +static ssize_t show_exynos_devfreq_get_freq(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + ssize_t count = 0; + u32 get_freq = 0; + + if (exynos_devfreq_get_freq(data->dev, &get_freq, data->clk, data)) + dev_err(data->dev, "failed get freq\n"); + + count = snprintf(buf, PAGE_SIZE, "%10u Khz\n", get_freq); + + return count; +} + +static int exynos_devfreq_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 ssize_t show_exynos_devfreq_cmu_dump(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + ssize_t count = 0; + + mutex_lock(&data->lock); + if (exynos_devfreq_cmu_dump(data)) + dev_err(data->dev, "failed CMU Dump\n"); + mutex_unlock(&data->lock); + + count = snprintf(buf, PAGE_SIZE, "Done\n"); + + return count; +} + +static ssize_t show_debug_scaling_devfreq_max(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + ssize_t count = 0; + int val; + + if (data->pm_qos_class_max) { + val = pm_qos_read_req_value(data->pm_qos_class_max, &data->debug_pm_qos_max); + if (val < 0) { + dev_err(dev, "failed to read requested value\n"); + return count; + } + count += snprintf(buf, PAGE_SIZE, "%d\n", val); + } + + return count; +} + +static ssize_t store_debug_scaling_devfreq_max(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + int ret; + u32 qos_value; + + ret = sscanf(buf, "%u", &qos_value); + if (ret != 1) + return -EINVAL; + + if (data->pm_qos_class_max) { + if (pm_qos_request_active(&data->debug_pm_qos_max)) + pm_qos_update_request(&data->debug_pm_qos_max, qos_value); + } + + return count; +} + +static ssize_t show_debug_scaling_devfreq_min(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + ssize_t count = 0; + int val; + + val = pm_qos_read_req_value(data->pm_qos_class, &data->debug_pm_qos_min); + if (val < 0) { + dev_err(dev, "failed to read requested value\n"); + return count; + } + + count += snprintf(buf, PAGE_SIZE, "%d\n", val); + + return count; +} + +static ssize_t store_debug_scaling_devfreq_min(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + int ret; + u32 qos_value; + + ret = sscanf(buf, "%u", &qos_value); + if (ret != 1) + return -EINVAL; + + if (pm_qos_request_active(&data->debug_pm_qos_min)) + pm_qos_update_request(&data->debug_pm_qos_min, qos_value); + + return count; +} + +static ssize_t show_exynos_devfreq_disable_pm_qos(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + ssize_t count = 0; + + count += snprintf(buf, PAGE_SIZE, "%s\n", + data->devfreq->disabled_pm_qos ? "disabled" : "enabled"); + + return count; +} + +static ssize_t store_exynos_devfreq_disable_pm_qos(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + int ret; + u32 disable; + + ret = sscanf(buf, "%u", &disable); + if (ret != 1) + return -EINVAL; + + if (disable) + data->devfreq->disabled_pm_qos = true; + else + data->devfreq->disabled_pm_qos = false; + + return count; +} +static DEVICE_ATTR(exynos_devfreq_info, 0640, show_exynos_devfreq_info, NULL); +static DEVICE_ATTR(exynos_devfreq_get_freq, 0640, show_exynos_devfreq_get_freq, NULL); +static DEVICE_ATTR(exynos_devfreq_cmu_dump, 0640, show_exynos_devfreq_cmu_dump, NULL); +static DEVICE_ATTR(debug_scaling_devfreq_min, 0640, show_debug_scaling_devfreq_min, store_debug_scaling_devfreq_min); +static DEVICE_ATTR(debug_scaling_devfreq_max, 0640, show_debug_scaling_devfreq_max, + store_debug_scaling_devfreq_max); +static DEVICE_ATTR(disable_pm_qos, 0640, show_exynos_devfreq_disable_pm_qos, + store_exynos_devfreq_disable_pm_qos); + +static struct attribute *exynos_devfreq_sysfs_entries[] = { + &dev_attr_exynos_devfreq_info.attr, + &dev_attr_exynos_devfreq_get_freq.attr, + &dev_attr_exynos_devfreq_cmu_dump.attr, + &dev_attr_debug_scaling_devfreq_min.attr, + &dev_attr_debug_scaling_devfreq_max.attr, + &dev_attr_disable_pm_qos.attr, + NULL, +}; + +static struct attribute_group exynos_devfreq_attr_group = { + .name = "exynos_data", + .attrs = exynos_devfreq_sysfs_entries, +}; +#endif + +static ssize_t show_scaling_devfreq_min(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + ssize_t count = 0; + int val; + + val = pm_qos_read_req_value(data->pm_qos_class, &data->sys_pm_qos_min); + if (val < 0) { + dev_err(dev, "failed to read requested value\n"); + return count; + } + + count += snprintf(buf, PAGE_SIZE, "%d\n", val); + + return count; +} + +static ssize_t store_scaling_devfreq_min(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + int ret; + u32 qos_value; + + ret = sscanf(buf, "%u", &qos_value); + if (ret != 1) + return -EINVAL; + + if (pm_qos_request_active(&data->sys_pm_qos_min)) + pm_qos_update_request(&data->sys_pm_qos_min, qos_value); + + return count; +} + +static DEVICE_ATTR(scaling_devfreq_min, 0640, show_scaling_devfreq_min, store_scaling_devfreq_min); + +/* get frequency and delay time data from string */ +static unsigned int *get_tokenized_data(const char *buf, int *num_tokens) +{ + const char *cp; + int i; + int ntokens = 1; + unsigned int *tokenized_data; + int err = -EINVAL; + + cp = buf; + while ((cp = strpbrk(cp + 1, " :"))) + ntokens++; + + if (!(ntokens & 0x1)) + goto err; + + tokenized_data = kmalloc(ntokens * sizeof(unsigned int), GFP_KERNEL); + if (!tokenized_data) { + err = -ENOMEM; + goto err; + } + + cp = buf; + i = 0; + while (i < ntokens) { + if (sscanf(cp, "%u", &tokenized_data[i++]) != 1) + goto err_kfree; + + cp = strpbrk(cp, " :"); + if (!cp) + break; + cp++; + } + + if (i != ntokens) + goto err_kfree; + + *num_tokens = ntokens; + return tokenized_data; + +err_kfree: + kfree(tokenized_data); +err: + return ERR_PTR(err); +} + +static ssize_t show_use_delay_time(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + ssize_t count = 0; + + mutex_lock(&data->devfreq->lock); + count += snprintf(buf, PAGE_SIZE, "%s\n", + (data->simple_interactive_data.use_delay_time) ? "true" : "false"); + mutex_unlock(&data->devfreq->lock); + return count; +} + +static ssize_t store_use_delay_time(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + int ret, use_delay_time; + + ret = sscanf(buf, "%d", &use_delay_time); + + if (ret != 1) + return -EINVAL; + + if (use_delay_time == 0 || use_delay_time == 1) { + mutex_lock(&data->devfreq->lock); + data->simple_interactive_data.use_delay_time = use_delay_time ? true : false; + mutex_unlock(&data->devfreq->lock); + } else { + dev_info(data->dev, "This is invalid value: %d\n", use_delay_time); + } + + return count; +} + +static ssize_t show_delay_time(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + ssize_t count = 0; + int i; + + mutex_lock(&data->devfreq->lock); + for (i = 0; i < data->simple_interactive_data.ndelay_time; i++) { + count += snprintf(buf + count, PAGE_SIZE, "%d%s", + data->simple_interactive_data.delay_time[i], + (i == data->simple_interactive_data.ndelay_time - 1) ? + "" : (i % 2) ? ":" : " "); + } + count += snprintf(buf + count, PAGE_SIZE, "\n"); + mutex_unlock(&data->devfreq->lock); + return count; +} + +static ssize_t store_delay_time(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct device *parent = dev->parent; + struct platform_device *pdev = container_of(parent, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + int ntokens; + int *new_delay_time = NULL; + + new_delay_time = get_tokenized_data(buf , &ntokens); + if (IS_ERR(new_delay_time)) + return PTR_RET(new_delay_time); + + mutex_lock(&data->devfreq->lock); + kfree(data->simple_interactive_data.delay_time); + data->simple_interactive_data.delay_time = new_delay_time; + data->simple_interactive_data.ndelay_time = ntokens; + mutex_unlock(&data->devfreq->lock); + + return count; +} + +static DEVICE_ATTR(use_delay_time, 0640, show_use_delay_time, store_use_delay_time); +static DEVICE_ATTR(delay_time, 0640, show_delay_time, store_delay_time); + +static struct attribute *devfreq_interactive_sysfs_entries[] = { + &dev_attr_use_delay_time.attr, + &dev_attr_delay_time.attr, + NULL, +}; + +static struct attribute_group devfreq_delay_time_attr_group = { + .name = "interactive", + .attrs = devfreq_interactive_sysfs_entries, +}; + +#ifdef CONFIG_EXYNOS_DVFS_MANAGER +int find_exynos_devfreq_dm_type(struct device *dev, int *dm_type) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + + *dm_type = data->dm_type; + + return 0; +} + +struct device *find_exynos_devfreq_device(void *devdata) +{ + struct exynos_devfreq_data *data = devdata; + + if (!devdata) { + pr_err("%s: failed get Devfreq type\n", __func__); + return ERR_PTR(-EINVAL); + } + + return data->dev; +} +#endif + +#ifdef CONFIG_OF +#if defined(CONFIG_ECT) +static int exynos_devfreq_parse_ect(struct exynos_devfreq_data *data, const char *dvfs_domain_name) +{ + int i; + void *dvfs_block; + struct ect_dvfs_domain *dvfs_domain; + + dvfs_block = ect_get_block(BLOCK_DVFS); + if (dvfs_block == NULL) + return -ENODEV; + + dvfs_domain = ect_dvfs_get_domain(dvfs_block, (char *)dvfs_domain_name); + if (dvfs_domain == NULL) + return -ENODEV; + + data->max_state = dvfs_domain->num_of_level; + data->opp_list = kzalloc(sizeof(struct exynos_devfreq_opp_table) * data->max_state, GFP_KERNEL); + if (!data->opp_list) { + pr_err("%s: failed to allocate opp_list\n", __func__); + return -ENOMEM; + } + + for (i = 0; i < dvfs_domain->num_of_level; ++i) { + data->opp_list[i].idx = i; + data->opp_list[i].freq = dvfs_domain->list_level[i].level; + data->opp_list[i].volt = 0; + } + + return 0; +} +#endif + +static int exynos_devfreq_parse_dt(struct device_node *np, struct exynos_devfreq_data *data) +{ + const char *use_acpm, *bts_update; +#if defined(CONFIG_ECT) + const char *devfreq_domain_name; +#endif + const char *buf; + const char *use_delay_time; + const char *pd_name; + const char *update_fvp; + int ntokens; + int not_using_ect = true; + + if (!np) + return -ENODEV; + + if (of_property_read_u32(np, "devfreq_type", &data->devfreq_type)) + return -ENODEV; + if (of_property_read_u32(np, "pm_qos_class", &data->pm_qos_class)) + return -ENODEV; + if (of_property_read_u32(np, "pm_qos_class_max", &data->pm_qos_class_max)) + return -ENODEV; + if (of_property_read_u32(np, "ess_flag", &data->ess_flag)) + return -ENODEV; + +#if defined(CONFIG_ECT) + if (of_property_read_string(np, "devfreq_domain_name", &devfreq_domain_name)) + return -ENODEV; + not_using_ect = exynos_devfreq_parse_ect(data, devfreq_domain_name); +#endif + if (not_using_ect) { + dev_err(data->dev, "cannot parse the DVFS info in ECT"); + return -ENODEV; + } + + if (of_property_read_string(np, "pd_name", &pd_name)) { + dev_info(data->dev, "no power domain\n"); + data->pm_domain = NULL; + } else { + dev_info(data->dev, "power domain: %s\n", pd_name); + data->pm_domain = exynos_pd_lookup_name(pd_name); + } + + + if (of_property_read_u32_array(np, "freq_info", (u32 *)&freq_array, + (size_t)(ARRAY_SIZE(freq_array)))) + return -ENODEV; + + data->devfreq_profile.initial_freq = freq_array[0]; + data->default_qos = freq_array[1]; + data->devfreq_profile.suspend_freq = freq_array[2]; + data->min_freq = freq_array[3]; + data->max_freq = freq_array[4]; + data->reboot_freq = freq_array[5]; + + if (of_property_read_u32_array(np, "boot_info", (u32 *)&boot_array, + (size_t)(ARRAY_SIZE(boot_array)))) { + data->boot_qos_timeout = 0; + data->boot_freq = 0; + dev_info(data->dev, "This doesn't use boot value\n"); + } else { + data->boot_qos_timeout = boot_array[0]; + data->boot_freq = boot_array[1]; + } + + if (of_property_read_u32(np, "governor", &data->gov_type)) + return -ENODEV; + if (data->gov_type == SIMPLE_INTERACTIVE) + data->governor_name = "interactive"; + else { + dev_err(data->dev, "invalid governor name (%s)\n", data->governor_name); + return -EINVAL; + } + + if (!of_property_read_string(np, "use_acpm", &use_acpm)) { + if (!strcmp(use_acpm, "true")) { + data->use_acpm = true; + } else { + data->use_acpm = false; + dev_info(data->dev, "This does not use acpm\n"); + } + } else { + dev_info(data->dev, "This does not use acpm\n"); + data->use_acpm = false; + } + + if (!of_property_read_string(np, "bts_update", &bts_update)) { + if (!strcmp(bts_update, "true")) { + data->bts_update = true; + } else { + data->bts_update = false; + dev_info(data->dev, "This does not bts update\n"); + } + } else { + dev_info(data->dev, "This does not bts update\n"); + data->bts_update = false; + } + + if (!of_property_read_string(np, "update_fvp", &update_fvp)) { + if (!strcmp(update_fvp, "true")) { + data->update_fvp = true; + } else { + data->update_fvp = false; + dev_info(data->dev, "This does not update fvp\n"); + } + } else { + dev_info(data->dev, "This does not update fvp\n"); + data->update_fvp = false; + } + + if (of_property_read_u32(np, "dfs_id", &data->dfs_id) && + of_property_match_string(np, "clock-names", buf)) + return -ENODEV; + + if (data->gov_type == SIMPLE_INTERACTIVE) { + if (of_property_read_string(np, "use_delay_time", &use_delay_time)) + return -ENODEV; + + if (!strcmp(use_delay_time, "true")) { + data->simple_interactive_data.use_delay_time = true; + } else if (!strcmp(use_delay_time, "false")) { + data->simple_interactive_data.use_delay_time = false; + } else { + dev_err(data->dev, "invalid use_delay_time : (%s)\n", use_delay_time); + return -EINVAL; + } + + if (data->simple_interactive_data.use_delay_time) { + if (of_property_read_string(np, "delay_time_list", &buf)) { + /* + * If there is not delay time list, + * delay time will be filled with default time + */ + data->simple_interactive_data.delay_time = + kmalloc(sizeof(unsigned int), GFP_KERNEL); + if (!data->simple_interactive_data.delay_time) { + dev_err(data->dev, "Fail to allocate delay_time memory\n"); + return -ENOMEM; + } + *(data->simple_interactive_data.delay_time) + = DEFAULT_DELAY_TIME; + data->simple_interactive_data.ndelay_time = + DEFAULT_NDELAY_TIME; + dev_info(data->dev, "set default delay time %d ms\n", + DEFAULT_DELAY_TIME); + } else { + data->simple_interactive_data.delay_time = + get_tokenized_data(buf, &ntokens); + data->simple_interactive_data.ndelay_time = ntokens; + } + } + } else { + dev_err(data->dev, "not support governor type %u\n", data->gov_type); + return -EINVAL; + } + +#ifdef CONFIG_EXYNOS_DVFS_MANAGER + if (of_property_read_u32(np, "dm-index", &data->dm_type)) { + dev_err(data->dev, "not support dvfs manager\n"); + return -ENODEV; + } +#endif + return 0; +} +#else +static int exynos_devfreq_parse_dt(struct device_node *np, struct exynos_devfrq_data *data) +{ + return -EINVAL; +} +#endif + +s32 exynos_devfreq_get_opp_idx(struct exynos_devfreq_opp_table *table, unsigned int size, u32 freq) +{ + int i; + + for (i = 0; i < size; ++i) { + if (table[i].freq == freq) + return i; + } + + return -ENODEV; +} + +static int exynos_init_freq_table(struct exynos_devfreq_data *data) +{ + int i, ret; + u32 freq, volt; + + for (i = 0; i < data->max_state; i++) { + freq = data->opp_list[i].freq; + volt = data->opp_list[i].volt; + + data->devfreq_profile.freq_table[i] = freq; + + ret = dev_pm_opp_add(data->dev, freq, volt); + if (ret) { + dev_err(data->dev, "failed to add opp entries %uKhz\n", freq); + return ret; + } else { + dev_info(data->dev, "DEVFREQ : %8uKhz, %8uuV\n", freq, volt); + } + } + + ret = exynos_devfreq_init_freq_table(data); + if (ret) { + dev_err(data->dev, "failed init frequency table\n"); + return ret; + } + + return 0; +} + +static int exynos_devfreq_reboot_notifier(struct notifier_block *nb, unsigned long val, void *v) +{ + struct exynos_devfreq_data *data = container_of(nb, struct exynos_devfreq_data, + reboot_notifier); + + if (pm_qos_request_active(&data->default_pm_qos_min)) + pm_qos_update_request(&data->default_pm_qos_min, data->reboot_freq); + + if (exynos_devfreq_reboot(data)) { + dev_err(data->dev, "failed reboot\n"); + return NOTIFY_BAD; + } + + return NOTIFY_OK; +} + +static int exynos_devfreq_target(struct device *dev, unsigned long *target_freq, u32 flags) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + struct timeval before_target, after_target, before_setfreq, after_setfreq; + struct dev_pm_opp *target_opp; + u32 target_volt; + s32 target_idx; + s32 target_time = 0; + int ret = 0; + + if (data->devfreq_disabled) + return -EAGAIN; + + do_gettimeofday(&before_target); + + mutex_lock(&data->lock); + + target_opp = devfreq_recommended_opp(dev, target_freq, flags); + if (IS_ERR(target_opp)) { + dev_err(dev, "not found valid OPP table\n"); + ret = PTR_ERR(target_opp); + goto out; + } + + *target_freq = dev_pm_opp_get_freq(target_opp); + target_volt = (u32)dev_pm_opp_get_voltage(target_opp); + dev_pm_opp_put(target_opp); + + target_idx = exynos_devfreq_get_opp_idx(data->opp_list, data->max_state, *target_freq); + if (target_idx < 0) { + ret = -EINVAL; + goto out; + } + + data->new_freq = (u32)(*target_freq); + data->new_idx = target_idx; + data->new_volt = target_volt; + + if (data->old_freq == data->new_freq) + goto out; + + dev_dbg(dev, "LV_%d, %uKhz, %uuV ======> LV_%d, %uKhz, %uuV\n", + data->old_idx, data->old_freq, data->old_volt, + data->new_idx, data->new_freq, data->new_volt); +#ifdef CONFIG_DEBUG_SNAPSHOT_FREQ + dbg_snapshot_freq(data->ess_flag, data->old_freq, data->new_freq, DSS_FLAG_IN); +#endif + do_gettimeofday(&before_setfreq); + + ret = exynos_devfreq_set_freq(dev, data->new_freq, data->clk, data); + if (ret) { + dev_err(dev, "failed set frequency (%uKhz --> %uKhz)\n", + data->old_freq, data->new_freq); + goto out; + } + + do_gettimeofday(&after_setfreq); +#ifdef CONFIG_DEBUG_SNAPSHOT_FREQ + dbg_snapshot_freq(data->ess_flag, data->old_freq, data->new_freq, DSS_FLAG_OUT); +#endif + + data->old_freq = data->new_freq; + data->old_idx = data->new_idx; + data->old_volt = data->new_volt; + +out: + mutex_unlock(&data->lock); + + do_gettimeofday(&after_target); + + target_time = (after_target.tv_sec - before_target.tv_sec) * USEC_PER_SEC + + (after_target.tv_usec - before_target.tv_usec); + + data->target_delay = target_time; + + dev_dbg(dev, "target time: %d usec\n", target_time); + + return ret; +} + +static int exynos_devfreq_suspend(struct device *dev) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); + int ret = 0; +#ifdef CONFIG_EXYNOS_DVFS_MANAGER +#ifdef CONFIG_EXYNOS_ACPM + int size, ch_num; + unsigned int cmd[4]; + struct ipc_config config; +#endif +#endif + u32 get_freq = 0; + +#ifdef CONFIG_EXYNOS_DVFS_MANAGER + if (data->use_acpm) { + mutex_lock(&data->devfreq->lock); + //send flag +#ifdef CONFIG_EXYNOS_ACPM + ret = acpm_ipc_request_channel(dev->of_node, NULL, &ch_num, &size); + if (ret) { + dev_err(dev, "acpm request channel is failed, id:%u, size:%u\n", ch_num, size); + mutex_unlock(&data->devfreq->lock); + return -EINVAL; + } + /* Initial value of release flag is true. + * "true" means state of AP is running + * "false means state of AP is sleep. + */ + config.cmd = cmd; + config.response = true; + config.indirection = false; + config.cmd[0] = data->devfreq_type; + config.cmd[1] = false; + config.cmd[2] = DATA_INIT; + config.cmd[3] = RELEASE; + + ret = acpm_ipc_send_data(ch_num, &config); + if (ret) { + dev_err(dev, "failed to send release infomation to FVP"); + mutex_unlock(&data->devfreq->lock); + return -EINVAL; + } +#endif + data->devfreq->str_freq = data->devfreq_profile.suspend_freq; + ret = update_devfreq(data->devfreq); + if (ret && ret != -EAGAIN) { + dev_err(&data->devfreq->dev, "devfreq failed with (%d) error\n", ret); + mutex_unlock(&data->devfreq->lock); + return NOTIFY_BAD; + } + mutex_unlock(&data->devfreq->lock); + } +#endif + if (!data->use_acpm && pm_qos_request_active(&data->default_pm_qos_min)) + pm_qos_update_request(&data->default_pm_qos_min, + data->devfreq_profile.suspend_freq); + if (exynos_devfreq_get_freq(data->dev, &get_freq, data->clk, data)) + dev_err(data->dev, "failed get freq\n"); + + dev_info(data->dev, "Suspend_frequency is %u\n", get_freq); + + return ret; +} + +static int exynos_devfreq_resume(struct device *dev) +{ + struct platform_device *pdev = container_of(dev, struct platform_device, dev); + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); +#ifdef CONFIG_EXYNOS_DVFS_MANAGER +#ifdef CONFIG_EXYNOS_ACPM + int size, ch_num; + unsigned int cmd[4]; + struct ipc_config config; +#endif +#endif + int ret = 0; + u32 cur_freq; + + if (!exynos_devfreq_get_freq(data->dev, &cur_freq, data->clk, data)) + dev_info(data->dev, "Resume frequency is %u\n", cur_freq); +#ifdef CONFIG_EXYNOS_DVFS_MANAGER + if (data->use_acpm) { + mutex_lock(&data->devfreq->lock); + //send flag +#ifdef CONFIG_EXYNOS_ACPM + ret = acpm_ipc_request_channel(dev->of_node, NULL, &ch_num, &size); + if (ret) { + dev_err(dev, "acpm request channel is failed, id:%u, size:%u\n", ch_num, size); + mutex_unlock(&data->devfreq->lock); + return -EINVAL; + } + + config.cmd = cmd; + config.response = true; + config.indirection = false; + config.cmd[0] = data->devfreq_type; + config.cmd[1] = true; + config.cmd[2] = DATA_INIT; + config.cmd[3] = RELEASE; + + ret = acpm_ipc_send_data(ch_num, &config); + if (ret) { + dev_err(dev, "failed to send release infomation to FVP"); + mutex_unlock(&data->devfreq->lock); + return -EINVAL; + } +#endif + data->devfreq->str_freq = 0; + ret = update_devfreq(data->devfreq); + if (ret && ret != -EAGAIN) { + dev_err(&data->devfreq->dev, "devfreq failed with (%d) error\n", ret); + mutex_unlock(&data->devfreq->lock); + return NOTIFY_BAD; + } + mutex_unlock(&data->devfreq->lock); + } +#endif + if (!data->use_acpm && pm_qos_request_active(&data->default_pm_qos_min)) + pm_qos_update_request(&data->default_pm_qos_min, data->default_qos); + + return ret; +} + +static int exynos_devfreq_probe(struct platform_device *pdev) +{ + int ret = 0; + struct exynos_devfreq_data *data; + struct dev_pm_opp *init_opp; + unsigned long init_freq = 0; +#ifdef CONFIG_EXYNOS_DVFS_MANAGER + int nr_constraint; +#endif + data = kzalloc(sizeof(struct exynos_devfreq_data), GFP_KERNEL); + if (data == NULL) { + dev_err(&pdev->dev, "failed to allocate devfreq data\n"); + ret = -ENOMEM; + goto err_data; + } + + data->dev = &pdev->dev; + + mutex_init(&data->lock); + + /* parsing devfreq dts data for exynos */ + ret = exynos_devfreq_parse_dt(data->dev->of_node, data); + if (ret) { + dev_err(data->dev, "failed to parse private data\n"); + goto err_parse_dt; + } + + data->devfreq_profile.max_state = data->max_state; + data->devfreq_profile.target = exynos_devfreq_target; + if (data->gov_type == SIMPLE_INTERACTIVE) { + data->simple_interactive_data.pm_qos_class = data->pm_qos_class; + data->simple_interactive_data.pm_qos_class_max = data->pm_qos_class_max; + data->governor_data = &data->simple_interactive_data; + } + + data->devfreq_profile.freq_table = kzalloc(sizeof(*(data->devfreq_profile.freq_table)) * data->max_state, GFP_KERNEL); + if (data->devfreq_profile.freq_table == NULL) { + dev_err(data->dev, "failed to allocate for freq_table\n"); + ret = -ENOMEM; + goto err_freqtable; + } + + ret = exynos_init_freq_table(data); + if (ret) { + dev_err(data->dev, "failed initailize freq_table\n"); + goto err_init_table; + } + + devfreq_data[data->devfreq_type] = data; + platform_set_drvdata(pdev, data); + + data->old_freq = (u32)data->devfreq_profile.initial_freq; + data->old_idx = exynos_devfreq_get_opp_idx(data->opp_list, data->max_state, data->old_freq); + if (data->old_idx < 0) { + ret = -EINVAL; + goto err_old_idx; + } + + init_freq = (unsigned long)data->old_freq; + init_opp = devfreq_recommended_opp(data->dev, &init_freq, 0); + if (IS_ERR(init_opp)) { + dev_err(data->dev, "not found valid OPP table for sync\n"); + ret = PTR_ERR(init_opp); + goto err_get_opp; + } + data->new_volt = (u32)dev_pm_opp_get_voltage(init_opp); + dev_pm_opp_put(init_opp); + + dev_info(data->dev, "Initial Frequency: %ld, Initial Voltage: %d\n", init_freq, + data->new_volt); + + data->old_volt = data->new_volt; +#ifdef CONFIG_EXYNOS_DVFS_MANAGER + ret = exynos_dm_data_init(data->dm_type, data, data->min_freq, data->max_freq, data->old_freq); + if (ret) { + dev_err(data->dev, "failed DVFS Manager data init\n"); + goto err_dm_data_init; + } + + for (nr_constraint = 0; nr_constraint < data->nr_constraint; nr_constraint++) { + if(data->constraint[nr_constraint]) { + ret = register_exynos_dm_constraint_table(data->dm_type, + data->constraint[nr_constraint]); + if (ret) { + dev_err(data->dev,"failed registration constraint table(%d)\n", + nr_constraint); + goto err_dm_table; + } + } + } +#endif + /* This flag guarantees initial frequency during boot time */ + data->devfreq_disabled = true; + + data->devfreq = devfreq_add_device(data->dev, &data->devfreq_profile, + data->governor_name, data->governor_data); + if (IS_ERR(data->devfreq)) { + dev_err(data->dev, "failed devfreq device added\n"); + ret = -EINVAL; + goto err_devfreq; + } + + data->devfreq->min_freq = data->min_freq; + data->devfreq->max_freq = data->max_freq; + + pm_qos_add_request(&data->sys_pm_qos_min, (int)data->pm_qos_class, data->min_freq); +#ifdef CONFIG_ARM_EXYNOS_DEVFREQ_DEBUG + pm_qos_add_request(&data->debug_pm_qos_min, (int)data->pm_qos_class, data->min_freq); + pm_qos_add_request(&data->debug_pm_qos_max, (int)data->pm_qos_class_max, data->max_freq); +#endif + if (data->pm_qos_class_max) + pm_qos_add_request(&data->default_pm_qos_max, (int)data->pm_qos_class_max, + data->max_freq); + pm_qos_add_request(&data->default_pm_qos_min, (int)data->pm_qos_class, data->default_qos); + pm_qos_add_request(&data->boot_pm_qos, (int)data->pm_qos_class, + data->devfreq_profile.initial_freq); + + ret = devfreq_register_opp_notifier(data->dev, data->devfreq); + if (ret) { + dev_err(data->dev, "failed register opp notifier\n"); + goto err_opp_noti; + } + + data->reboot_notifier.notifier_call = exynos_devfreq_reboot_notifier; + ret = register_reboot_notifier(&data->reboot_notifier); + if (ret) { + dev_err(data->dev, "failed register reboot notifier\n"); + goto err_reboot_noti; + } + + ret = sysfs_create_file(&data->devfreq->dev.kobj, &dev_attr_scaling_devfreq_min.attr); + if (ret) + dev_warn(data->dev, "failed create sysfs for devfreq pm_qos_min\n"); + +#ifdef CONFIG_ARM_EXYNOS_DEVFREQ_DEBUG + ret = sysfs_create_group(&data->devfreq->dev.kobj, &exynos_devfreq_attr_group); + if (ret) + dev_warn(data->dev, "failed create sysfs for devfreq data\n"); +#endif + ret = sysfs_create_group(&data->devfreq->dev.kobj, &devfreq_delay_time_attr_group); + if (ret) + dev_warn(data->dev, "failed create sysfs for devfreq data\n"); + + data->devfreq_disabled = false; + + if (!data->pm_domain) { + /* set booting frequency during booting time */ + pm_qos_update_request_timeout(&data->boot_pm_qos, data->boot_freq, + data->boot_qos_timeout * USEC_PER_SEC); + } else { + pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + pm_qos_update_request(&data->boot_pm_qos, data->default_qos); + pm_runtime_put_sync(&pdev->dev); + } + + dev_info(data->dev, "devfreq is initialized!!\n"); + + return 0; + +err_reboot_noti: + devfreq_unregister_opp_notifier(data->dev, data->devfreq); +err_opp_noti: + pm_qos_remove_request(&data->boot_pm_qos); + pm_qos_remove_request(&data->default_pm_qos_min); + if (data->pm_qos_class_max) + pm_qos_remove_request(&data->default_pm_qos_max); +#ifdef CONFIG_ARM_EXYNOS_DEVFREQ_DEBUG + pm_qos_remove_request(&data->debug_pm_qos_min); + pm_qos_remove_request(&data->debug_pm_qos_max); +#endif + pm_qos_remove_request(&data->sys_pm_qos_min); + devfreq_remove_device(data->devfreq); +err_devfreq: +#ifdef CONFIG_EXYNOS_DVFS_MANAGER + for (; nr_constraint >= 0; nr_constraint--) { + if (data->constraint[nr_constraint]) + unregister_exynos_dm_constraint_table(data->dm_type, + data->constraint[nr_constraint]); + } +err_dm_table: +err_dm_data_init: +#endif +err_get_opp: +err_old_idx: + platform_set_drvdata(pdev, NULL); +err_init_table: + kfree(data->devfreq_profile.freq_table); +err_freqtable: +err_parse_dt: + mutex_destroy(&data->lock); + kfree(data); +err_data: + + return ret; +} + +static int exynos_devfreq_remove(struct platform_device *pdev) +{ + struct exynos_devfreq_data *data = platform_get_drvdata(pdev); +#ifdef CONFIG_EXYNOS_DVFS_MANAGER + int nr_constraint; +#endif + sysfs_remove_file(&data->devfreq->dev.kobj, &dev_attr_scaling_devfreq_min.attr); +#ifdef CONFIG_ARM_EXYNOS_DEVFREQ_DEBUG + sysfs_remove_group(&data->devfreq->dev.kobj, &exynos_devfreq_attr_group); +#endif + sysfs_remove_group(&data->devfreq->dev.kobj, &devfreq_delay_time_attr_group); + + unregister_reboot_notifier(&data->reboot_notifier); + devfreq_unregister_opp_notifier(data->dev, data->devfreq); + + pm_qos_remove_request(&data->boot_pm_qos); + pm_qos_remove_request(&data->default_pm_qos_min); + if (data->pm_qos_class_max) + pm_qos_remove_request(&data->default_pm_qos_max); +#ifdef CONFIG_ARM_EXYNOS_DEVFREQ_DEBUG + pm_qos_remove_request(&data->debug_pm_qos_min); + pm_qos_remove_request(&data->debug_pm_qos_max); +#endif + pm_qos_remove_request(&data->sys_pm_qos_min); + devfreq_remove_device(data->devfreq); +#ifdef CONFIG_EXYNOS_DVFS_MANAGER + for (nr_constraint = 0; nr_constraint < data->nr_constraint; nr_constraint++) { + if (data->constraint[nr_constraint]) + unregister_exynos_dm_constraint_table(data->dm_type, + data->constraint[nr_constraint]); + } +#endif + platform_set_drvdata(pdev, NULL); + kfree(data->devfreq_profile.freq_table); + mutex_destroy(&data->lock); + kfree(data); + + return 0; +} + +static struct platform_device_id exynos_devfreq_driver_ids[] = { + { + .name = EXYNOS_DEVFREQ_MODULE_NAME, + }, + {}, +}; + +MODULE_DEVICE_TABLE(platform, exynos_devfreq_driver_ids); + +static const struct of_device_id exynos_devfreq_match[] = { + { + .compatible = "samsung,exynos-devfreq", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, exynos_devfreq_match); + +static const struct dev_pm_ops exynos_devfreq_pm_ops = { + .suspend_late = exynos_devfreq_suspend, + .resume_early = exynos_devfreq_resume, +}; + +static struct platform_driver exynos_devfreq_driver = { + .probe = exynos_devfreq_probe, + .remove = exynos_devfreq_remove, + .id_table = exynos_devfreq_driver_ids, + .driver = { + .name = EXYNOS_DEVFREQ_MODULE_NAME, + .owner = THIS_MODULE, + .pm = &exynos_devfreq_pm_ops, + .of_match_table = exynos_devfreq_match, + }, +}; + +static int exynos_devfreq_root_probe(struct platform_device *pdev) +{ + struct device_node *np; + int num_domains; + + np = pdev->dev.of_node; + + platform_driver_register(&exynos_devfreq_driver); + + /* alloc memory for devfreq data structure */ + num_domains = of_get_child_count(np); + devfreq_data = (struct exynos_devfreq_data **)kzalloc(sizeof(struct exynos_devfreq_data *) + * num_domains, GFP_KERNEL); + + /* probe each devfreq node */ + of_platform_populate(np, NULL, NULL, NULL); + + return 0; +} + +static const struct of_device_id exynos_devfreq_root_match[] = { + { + .compatible = "samsung,exynos-devfreq-root", + }, + {}, + }; + +static struct platform_driver exynos_devfreq_root_driver = { + .probe = exynos_devfreq_root_probe, + .driver = { + .name = "exynos-devfreq-root", + .owner = THIS_MODULE, + .of_match_table = exynos_devfreq_root_match, + }, +}; + +module_platform_driver(exynos_devfreq_root_driver); +MODULE_AUTHOR("Taekki Kim "); +MODULE_DESCRIPTION("Samsung EXYNOS Soc series devfreq common driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/exynos/exynos_ppmu.c b/drivers/devfreq/exynos/exynos_ppmu.c new file mode 100644 index 000000000000..551373c8aec4 --- /dev/null +++ b/drivers/devfreq/exynos/exynos_ppmu.c @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * EXYNOS - PPMU 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. + */ + +#include +#include +#include + +#include "exynos_ppmu.h" + +#define PPMU_PMNC 0x4 +#define PPMU_CNTENS 0x8 +#define PPMU_CNT_AUTO 0x30 +#define PPMU_PMCNT0_HIGH 0x004C +#define PPMU_PMCNT1_HIGH 0x0050 +#define PPMU_PMCNT2_HIGH 0x0054 +#define PPMU_PMCNT3_HIGH 0x0044 +#define PPMU_CCNT_HIGH 0x0058 +#define PPMU_PMCNT0_LOW 0x0034 +#define PPMU_PMCNT1_LOW 0x0038 +#define PPMU_PMCNT2_LOW 0x003C +#define PPMU_PMCNT3_LOW 0x0040 +#define PPMU_CCNT_LOW 0x0048 +#define PPMU_CH_EV0_TYPE 0x200 +#define PPMU_CH_EV1_TYPE 0x204 +#define PPMU_CH_EV2_TYPE 0x208 +#define PPMU_CH_EV3_TYPE 0x20c +#define PPMU_SM_ID_V 0x220 +#define PPMU_SM_ID_A 0x224 + +#define EVENT_RD_ACTIVATED 0x0 +#define EVENT_WR_ACTIVATED 0x1 +#define EVENT_RD_DATA 0x4 +#define EVENT3_RD_DATA 0x4 +#define EVENT3_WR_DATA 0x5 +#define EVENT3_RW_DATA 0x22 + + +/* 0x1: disable Q channel */ +/* auto mode */ +#define BIT_REGVALUE ((0x1<<24) | (0x1<<20)) +#define BIT_CH_CCNT (0x1<<31) +#define BIT_CH_PMCNT0 (0x1<<0) +#define BIT_CH_PMCNT1 (0x1<<1) +#define BIT_CH_PMCNT2 (0x1<<2) +#define BIT_CH_PMCNT3 (0x1<<3) +#define BIT_CH_ALL (BIT_CH_CCNT | BIT_CH_PMCNT0 | \ + BIT_CH_PMCNT1 | BIT_CH_PMCNT2 | \ + BIT_CH_PMCNT3) + +void exynos_read_ppmu(struct ppmu_data *ppmu, void __iomem *ppmu_base, + u32 channel) +{ + if (!channel) + channel = BIT_CH_ALL; + if (channel & BIT_CH_CCNT) + ppmu->ccnt = __raw_readl(ppmu_base + PPMU_CCNT_LOW); + if (channel & BIT_CH_PMCNT0) + ppmu->pmcnt0 = __raw_readl(ppmu_base + PPMU_PMCNT0_LOW); + if (channel & BIT_CH_PMCNT1) + ppmu->pmcnt1 = __raw_readl(ppmu_base + PPMU_PMCNT1_LOW); + if (channel & BIT_CH_PMCNT2) + ppmu->pmcnt2 = __raw_readl(ppmu_base + PPMU_PMCNT2_LOW); + if (channel & BIT_CH_PMCNT3) + ppmu->pmcnt3 = __raw_readl(ppmu_base + PPMU_PMCNT3_LOW); +} + +void exynos_init_ppmu(void __iomem *ppmu_base, u32 mask_v, u32 mask_a) +{ + + __raw_writel(BIT_REGVALUE | 0x6, ppmu_base + PPMU_PMNC); + /* Count Enable CCNT, PMCNTTx */ + __raw_writel(BIT_CH_ALL, ppmu_base + PPMU_CNTENS); + __raw_writel(EVENT_RD_ACTIVATED, ppmu_base + PPMU_CH_EV0_TYPE); + __raw_writel(EVENT_WR_ACTIVATED, ppmu_base + PPMU_CH_EV1_TYPE); + __raw_writel(EVENT_RD_DATA, ppmu_base + PPMU_CH_EV2_TYPE); + __raw_writel(EVENT3_WR_DATA, ppmu_base + PPMU_CH_EV3_TYPE); + if (mask_v) { + __raw_writel(mask_v, ppmu_base + PPMU_SM_ID_V); + __raw_writel(mask_a, ppmu_base + PPMU_SM_ID_A); + } +} + +void exynos_reset_ppmu(void __iomem *ppmu_base, u32 channel) +{ + if (!channel) + channel = BIT_CH_ALL; + __raw_writel(channel, ppmu_base + PPMU_CNT_AUTO); +} + +void exynos_start_ppmu(void __iomem *ppmu_base) +{ + __raw_writel(BIT_REGVALUE | 0x1, ppmu_base + PPMU_PMNC); +} + +void exynos_stop_ppmu(void __iomem *ppmu_base) +{ + __raw_writel(BIT_REGVALUE, ppmu_base + PPMU_PMNC); +} + +void exynos_exit_ppmu(void __iomem *ppmu_base) +{ + __raw_writel(BIT_REGVALUE, ppmu_base + PPMU_PMNC); +} diff --git a/drivers/devfreq/exynos/exynos_ppmu.h b/drivers/devfreq/exynos/exynos_ppmu.h new file mode 100644 index 000000000000..92550ef61bf8 --- /dev/null +++ b/drivers/devfreq/exynos/exynos_ppmu.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * EXYNOS PPMU header + * + * 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 __DEVFREQ_EXYNOS_PPMU_H +#define __DEVFREQ_EXYNOS_PPMU_H __FILE__ + +#include + +struct ppmu_data { + u64 ccnt; + u64 pmcnt0; + u64 pmcnt1; + u64 pmcnt2; + u64 pmcnt3; +}; + +#define exynos_read_ppmu(a, ...) do {} while(0) +#define exynos_init_ppmu(a, ...) do {} while(0) +#define exynos_exit_ppmu(a, ...) do {} while(0) +#define exynos_reset_ppmu(a, ...) do {} while(0) +#define exynos_start_ppmu(a, ...) do {} while(0) +#define exynos_stop_ppmu(a, ...) do {} while(0) + +#endif /* __DEVFREQ_EXYNOS_PPMU_H */ diff --git a/drivers/devfreq/governor_simpleexynos.c b/drivers/devfreq/governor_simpleexynos.c new file mode 100644 index 000000000000..9a03f88d7b66 --- /dev/null +++ b/drivers/devfreq/governor_simpleexynos.c @@ -0,0 +1,261 @@ +/* + * linux/drivers/devfreq/governor_simpleexynos.c + * + * Copyright (C) 2011 Samsung Electronics + * MyungJoo Ham + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "governor.h" + +static int gov_simple_exynos = 0; + +static int devfreq_simple_exynos_notifier(struct notifier_block *nb, unsigned long val, + void *v) +{ + struct devfreq_notifier_block *devfreq_nb; + + devfreq_nb = container_of(nb, struct devfreq_notifier_block, nb); + + mutex_lock(&devfreq_nb->df->lock); + update_devfreq(devfreq_nb->df); + mutex_unlock(&devfreq_nb->df->lock); + + return NOTIFY_OK; +} + +/* Default constants for DevFreq-Simple-Exynos(DFE) */ +#define DFE_URGENTTHRESHOLD (65) +#define DFE_UPTHRESHOLD (60) +#define DFE_DOWNTHRESHOLD (45) +#define DFE_IDLETHRESHOLD (30) + +static int devfreq_simple_exynos_func(struct devfreq *df, + unsigned long *freq) +{ + struct devfreq_dev_status stat; + unsigned int dfe_urgentthreshold = DFE_URGENTTHRESHOLD; + unsigned int dfe_upthreshold = DFE_UPTHRESHOLD; + unsigned int dfe_downthreshold = DFE_DOWNTHRESHOLD; + unsigned int dfe_idlethreshold = DFE_IDLETHRESHOLD; + struct devfreq_simple_exynos_data *data = df->data; + unsigned long max = (df->max_freq) ? df->max_freq : UINT_MAX; + unsigned long pm_qos_min = 0, cal_qos_max = 0; + unsigned long pm_qos_max = 0; + unsigned long usage_rate; + int err; + + if (data) { + if (!df->disabled_pm_qos) { + pm_qos_min = pm_qos_request(data->pm_qos_class); + if (data->pm_qos_class_max) + pm_qos_max = pm_qos_request(data->pm_qos_class_max); + if (unlikely(gov_simple_exynos)) + printk("pm_qos: %lu\n", pm_qos_min); + } + + if (data->urgentthreshold) + dfe_urgentthreshold = data->urgentthreshold; + if (data->upthreshold) + dfe_upthreshold = data->upthreshold; + if (data->downthreshold) + dfe_downthreshold = data->downthreshold; + if (data->idlethreshold) + dfe_idlethreshold = data->idlethreshold; + + if (data->cal_qos_max) { + cal_qos_max = data->cal_qos_max; + max = (df->max_freq) ? df->max_freq : 0; + } else { + cal_qos_max = df->max_freq; + } + } + + if (df->profile->get_dev_status) { + err = df->profile->get_dev_status(df->dev.parent, &stat); + if (err) + return err; + } else { + *freq = pm_qos_min; + if (pm_qos_max) + *freq = min(pm_qos_max, *freq); + return 0; + } + + /* Assume MAX if it is going to be divided by zero */ + if (stat.total_time == 0) { + *freq = max3(max, cal_qos_max, pm_qos_min); + if (pm_qos_max) + *freq = min(pm_qos_max, *freq); + return 0; + } + + /* Set MAX if we do not know the initial frequency */ + if (stat.current_frequency == 0) { + *freq = max3(max, cal_qos_max, pm_qos_min); + if (pm_qos_max) + *freq = min(pm_qos_max, *freq); + return 0; + } + + usage_rate = div64_u64(stat.busy_time * 100, stat.total_time); + + /* Set MAX if it's busy enough */ + if (usage_rate > dfe_urgentthreshold) + *freq = max3(max, cal_qos_max, pm_qos_min); + else if (usage_rate >= dfe_upthreshold) { + if (data) + *freq = data->above_freq; + } else if (usage_rate > dfe_downthreshold) + *freq = stat.current_frequency; + else if (usage_rate > dfe_idlethreshold) { + if (data) + *freq = data->below_freq; + } else + *freq = 0; + + if (*freq > cal_qos_max) + *freq = cal_qos_max; + + if (pm_qos_min) + *freq = max(pm_qos_min, *freq); + + if (pm_qos_max) + *freq = min(pm_qos_max, *freq); + + if (unlikely(gov_simple_exynos)) + printk("Usage: %lu, freq: %lu, old: %lu\n", usage_rate, *freq, stat.current_frequency); + + return 0; +} + +static int devfreq_simple_exynos_register_notifier(struct devfreq *df) +{ + int ret; + struct devfreq_simple_exynos_data *data = df->data; + + if (!data) + return -EINVAL; + + data->nb.df = df; + data->nb.nb.notifier_call = devfreq_simple_exynos_notifier; + + ret = pm_qos_add_notifier(data->pm_qos_class, &data->nb.nb); + if (ret < 0) + goto err1; + + if (data->pm_qos_class_max) { + data->nb_max.df = df; + data->nb_max.nb.notifier_call = devfreq_simple_exynos_notifier; + + ret = pm_qos_add_notifier(data->pm_qos_class_max, &data->nb_max.nb); + if (ret < 0) { + pm_qos_remove_notifier(data->pm_qos_class, &data->nb.nb); + goto err2; + } + } + + return 0; +err2: + kfree((void *)&data->nb_max.nb); + +err1: + kfree((void *)&data->nb.nb); + + return ret; +} + +static int devfreq_simple_exynos_unregister_notifier(struct devfreq *df) +{ + int ret; + struct devfreq_simple_exynos_data *data = df->data; + + if (!data) + return -EINVAL; + + if (data->pm_qos_class_max) { + ret = pm_qos_remove_notifier(data->pm_qos_class_max, &data->nb_max.nb); + if (ret < 0) + goto err; + } + + ret = pm_qos_remove_notifier(data->pm_qos_class, &data->nb.nb); + +err: + return ret; +} + +static int devfreq_simple_exynos_handler(struct devfreq *devfreq, + unsigned int event, void *data) +{ + int ret; + + switch (event) { + case DEVFREQ_GOV_START: + ret = devfreq_simple_exynos_register_notifier(devfreq); + if (ret) + return ret; + devfreq_monitor_start(devfreq); + break; + + case DEVFREQ_GOV_STOP: + devfreq_monitor_stop(devfreq); + ret = devfreq_simple_exynos_unregister_notifier(devfreq); + if (ret) + return ret; + break; + + case DEVFREQ_GOV_INTERVAL: + devfreq_interval_update(devfreq, (unsigned int *)data); + break; + + case DEVFREQ_GOV_SUSPEND: + devfreq_monitor_suspend(devfreq); + break; + + case DEVFREQ_GOV_RESUME: + devfreq_monitor_resume(devfreq); + break; + + default: + break; + } + + return 0; +} + +static struct devfreq_governor devfreq_simple_exynos = { + .name = "simple_exynos", + .get_target_freq = devfreq_simple_exynos_func, + .event_handler = devfreq_simple_exynos_handler, +}; + +static int __init devfreq_simple_exynos_init(void) +{ + return devfreq_add_governor(&devfreq_simple_exynos); +} +subsys_initcall(devfreq_simple_exynos_init); + +static void __exit devfreq_simple_exynos_exit(void) +{ + int ret; + + ret = devfreq_remove_governor(&devfreq_simple_exynos); + if (ret) + pr_err("%s: failed remove governor %d\n", __func__, ret); + + return; +} +module_exit(devfreq_simple_exynos_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/governor_simpleinteractive.c b/drivers/devfreq/governor_simpleinteractive.c new file mode 100644 index 000000000000..211d21becb08 --- /dev/null +++ b/drivers/devfreq/governor_simpleinteractive.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "governor.h" + +static int devfreq_simple_interactive_notifier(struct notifier_block *nb, unsigned long val, + void *v) +{ + struct devfreq_notifier_block *devfreq_nb; + + devfreq_nb = container_of(nb, struct devfreq_notifier_block, nb); + + mutex_lock(&devfreq_nb->df->lock); + update_devfreq(devfreq_nb->df); + mutex_unlock(&devfreq_nb->df->lock); + + return NOTIFY_OK; +} + +static int devfreq_simple_interactive_func(struct devfreq *df, + unsigned long *freq) +{ + struct devfreq_simple_interactive_data *data = df->data; + unsigned long pm_qos_min = 0; + unsigned long pm_qos_max = INT_MAX; + int delay_check = 0; + int delay_time = 0; + int i = 0; + struct dev_pm_opp *limit_opp; + + if (!data) { + pr_err("%s: failed to find governor data\n", __func__); + return -ENODATA; + } + + if (!df->disabled_pm_qos) { + pm_qos_min = pm_qos_request(data->pm_qos_class); + if (data->pm_qos_class_max) { + pm_qos_max = pm_qos_request(data->pm_qos_class_max); + limit_opp = devfreq_recommended_opp(df->dev.parent, &pm_qos_max, + DEVFREQ_FLAG_LEAST_UPPER_BOUND); + if (IS_ERR(limit_opp)) { + pr_err("%s: failed to limit by max frequency\n", __func__); + return PTR_ERR(limit_opp); + } + dev_pm_opp_put(limit_opp); + } + } + + *freq = pm_qos_min; + + if (!data->use_delay_time) + goto out; + + if (data->prev_freq != df->previous_freq) { + for (i = 0; i < data->ndelay_time - 1 && + *freq >= data->delay_time[i + 1]; i += 2) + ; + + /* unit of delay time should be 10msec */ + delay_check = data->delay_time[i] % DELAY_TIME_RANGE; + delay_time = delay_check ? + data->delay_time[i] - delay_check + DELAY_TIME_RANGE : + data->delay_time[i]; + + data->freq_timer.expires = data->changed_time + + msecs_to_jiffies(delay_time); + } + + if (pm_qos_max > df->previous_freq && *freq < df->previous_freq && + data->freq_timer.expires > jiffies) { + *freq = df->previous_freq; + if (!timer_pending(&data->freq_timer)) + /* timer is bound to cpu0 */ + add_timer_on(&data->freq_timer, BOUND_CPU_NUM); + + goto out; + } else if (timer_pending(&data->freq_timer)) { + del_timer_sync(&data->freq_timer); + } + + data->changed_time = jiffies; + +out: + /* + * save current frequency and time + * to use when update_devfreq is called next + */ + data->prev_freq = df->previous_freq; + *freq = min(pm_qos_max, *freq); + + return 0; +} + +static int devfreq_change_freq_task(void *data) +{ + struct devfreq *df = data; + + while (!kthread_should_stop()) { + set_current_state(TASK_INTERRUPTIBLE); + + schedule(); + + set_current_state(TASK_RUNNING); + + mutex_lock(&df->lock); + update_devfreq(df); + mutex_unlock(&df->lock); + } + + return 0; +} + +/*timer callback function send a signal */ +static void simple_interactive_timer(unsigned long data) +{ + struct devfreq_simple_interactive_data *gov_data = (void *)data; + + wake_up_process(gov_data->change_freq_task); +} + +static int devfreq_simple_interactive_register_notifier(struct devfreq *df) +{ + int ret; + struct devfreq_simple_interactive_data *data = df->data; + + if (!data) + return -EINVAL; + + data->nb.df = df; + data->nb.nb.notifier_call = devfreq_simple_interactive_notifier; + + ret = pm_qos_add_notifier(data->pm_qos_class, &data->nb.nb); + if (ret < 0) + goto err1; + + if (data->pm_qos_class_max) { + data->nb_max.df = df; + data->nb_max.nb.notifier_call = devfreq_simple_interactive_notifier; + + ret = pm_qos_add_notifier(data->pm_qos_class_max, &data->nb_max.nb); + if (ret < 0) { + pm_qos_remove_notifier(data->pm_qos_class, &data->nb.nb); + goto err2; + } + } + + /* timer of governor for delay time initialize */ + data->freq_timer.data = (unsigned long)data; + data->freq_timer.function = simple_interactive_timer; + init_timer(&data->freq_timer); + + data->change_freq_task = kthread_create(devfreq_change_freq_task, df, "simpleinteractive"); + + if (IS_ERR(data->change_freq_task)) { + pr_err("%s: failed kthread_create for simpleinteractive governor\n", __func__); + ret = PTR_ERR(data->change_freq_task); + + destroy_timer_on_stack(&data->freq_timer); + pm_qos_remove_notifier(data->pm_qos_class, &data->nb.nb); + if (data->pm_qos_class_max) + pm_qos_remove_notifier(data->pm_qos_class_max, &data->nb_max.nb); + + goto err2; + } + + kthread_bind(data->change_freq_task, BOUND_CPU_NUM); + + return 0; + +err2: + kfree((void *)&data->nb_max.nb); + +err1: + kfree((void *)&data->nb.nb); + + return ret; +} + +static int devfreq_simple_interactive_unregister_notifier(struct devfreq *df) +{ + int ret; + struct devfreq_simple_interactive_data *data = df->data; + + if (!data) + return -EINVAL; + + if (data->pm_qos_class_max) { + ret = pm_qos_remove_notifier(data->pm_qos_class_max, &data->nb_max.nb); + if (ret < 0) + goto err; + } + + ret = pm_qos_remove_notifier(data->pm_qos_class, &data->nb.nb); + + destroy_timer_on_stack(&data->freq_timer); + kthread_stop(data->change_freq_task); + +err: + return ret; +} + +static int devfreq_simple_interactive_handler(struct devfreq *devfreq, + unsigned int event, void *data) +{ + int ret; + + switch (event) { + case DEVFREQ_GOV_START: + ret = devfreq_simple_interactive_register_notifier(devfreq); + if (ret) + return ret; + break; + + case DEVFREQ_GOV_STOP: + ret = devfreq_simple_interactive_unregister_notifier(devfreq); + if (ret) + return ret; + break; + + default: + break; + } + + return 0; +} + +static struct devfreq_governor devfreq_simple_interactive = { + .name = "interactive", + .get_target_freq = devfreq_simple_interactive_func, + .event_handler = devfreq_simple_interactive_handler, +}; + +static int __init devfreq_simple_interactive_init(void) +{ + return devfreq_add_governor(&devfreq_simple_interactive); +} +subsys_initcall(devfreq_simple_interactive_init); + +static void __exit devfreq_simple_interactive_exit(void) +{ + int ret; + + ret = devfreq_remove_governor(&devfreq_simple_interactive); + if (ret) + pr_err("%s: failed remove governor %d\n", __func__, ret); + + return; +} +module_exit(devfreq_simple_interactive_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/devfreq/governor_simpleusage.c b/drivers/devfreq/governor_simpleusage.c new file mode 100644 index 000000000000..e180cbe91887 --- /dev/null +++ b/drivers/devfreq/governor_simpleusage.c @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * 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 +#include +#include +#include +#include +#include + +#include "governor.h" + +/* Default constants for DevFreq-Simple-Ondemand (DFSO) */ +#define DFSO_UPTHRESHOLD (50) +#define DFSO_TARGET_PERCENTAGE (20) +#define DFSO_PROPORTIONAL (120) +#define DFSO_WEIGHT (100) + +static int devfreq_simple_usage_notifier(struct notifier_block *nb, unsigned long val, void *data) +{ + struct devfreq_notifier_block *devfreq_nb; + struct devfreq_simple_usage_data *simple_usage_data; + + devfreq_nb = container_of(nb, struct devfreq_notifier_block, nb); + simple_usage_data = container_of(devfreq_nb, struct devfreq_simple_usage_data, nb); + devfreq_nb = &simple_usage_data->nb; + + mutex_lock(&devfreq_nb->df->lock); + update_devfreq(devfreq_nb->df); + mutex_unlock(&devfreq_nb->df->lock); + + return NOTIFY_OK; +} + +static int devfreq_simple_usage_func(struct devfreq *df, unsigned long *freq) +{ + struct devfreq_dev_status stat; + int err = df->profile->get_dev_status(df->dev.parent, &stat); + unsigned long long a, b; + unsigned int dfso_upthreshold = DFSO_UPTHRESHOLD; + unsigned int dfso_target_percentage = DFSO_TARGET_PERCENTAGE; + unsigned int dfso_proportional = DFSO_PROPORTIONAL; + unsigned int dfso_multiplication_weight = DFSO_WEIGHT; + struct devfreq_simple_usage_data *data = df->data; + unsigned long max = (df->max_freq) ? df->max_freq : 0; + unsigned long pm_qos_min; + + if (!data) + return -EINVAL; + + if (!df->disabled_pm_qos) + pm_qos_min = pm_qos_request(data->pm_qos_class); + + if (err) + return err; + + if (data->upthreshold) + dfso_upthreshold = data->upthreshold; + if (data->target_percentage) + dfso_target_percentage = data->target_percentage; + if (data->proportional) + dfso_proportional = data->proportional; + if (data->multiplication_weight) + dfso_multiplication_weight = data->multiplication_weight; + + a = stat.busy_time * dfso_multiplication_weight; + a = div64_u64(a, 100); + a = a * dfso_proportional; + b = div64_u64(a, stat.total_time); + + /* If percentage is larger than upthreshold, set with max freq */ + if (b >= data->upthreshold) { + max = max(data->cal_qos_max, pm_qos_min); + *freq = max; + + if (*freq > df->max_freq) + *freq = df->max_freq; + + return 0; + } + + b *= stat.current_frequency; + + a = div64_u64(b, dfso_target_percentage); + + if (a > data->cal_qos_max) + a = data->cal_qos_max; + + *freq = (unsigned long) a; + + if (pm_qos_min && *freq < pm_qos_min) + *freq = pm_qos_min; + + return 0; +} + +static int devfreq_simple_usage_register_notifier(struct devfreq *df) +{ + int ret; + struct devfreq_simple_usage_data *data = df->data; + + if (!data) + return -EINVAL; + + data->nb.df = df; + data->nb.nb.notifier_call = devfreq_simple_usage_notifier; + + ret = pm_qos_add_notifier(data->pm_qos_class, &data->nb.nb); + if (ret < 0) + goto err; + + return 0; +err: + kfree((void *)&data->nb.nb); + + return ret; +} + +static int devfreq_simple_usage_unregister_notifier(struct devfreq *df) +{ + struct devfreq_simple_usage_data *data = df->data; + + return pm_qos_remove_notifier(data->pm_qos_class, &data->nb.nb); +} + +static int devfreq_simple_usage_handler(struct devfreq *devfreq, + unsigned int event, void *data) +{ + int ret; + + switch (event) { + case DEVFREQ_GOV_START: + ret = devfreq_simple_usage_register_notifier(devfreq); + if (ret) + return ret; + devfreq_monitor_start(devfreq); + break; + + case DEVFREQ_GOV_STOP: + devfreq_monitor_stop(devfreq); + ret = devfreq_simple_usage_unregister_notifier(devfreq); + if (ret) + return ret; + break; + + case DEVFREQ_GOV_INTERVAL: + devfreq_interval_update(devfreq, (unsigned int*)data); + break; + + case DEVFREQ_GOV_SUSPEND: + devfreq_monitor_suspend(devfreq); + break; + + case DEVFREQ_GOV_RESUME: + devfreq_monitor_resume(devfreq); + break; + + default: + break; + } + + return 0; +} + +static struct devfreq_governor devfreq_simple_usage = { + .name = "simple_usage", + .get_target_freq = devfreq_simple_usage_func, + .event_handler = devfreq_simple_usage_handler, +}; + +static int __init devfreq_simple_usage_init(void) +{ + return devfreq_add_governor(&devfreq_simple_usage); +} +subsys_initcall(devfreq_simple_usage_init); + +static void __exit devfreq_simple_usage_exit(void) +{ + int ret; + + ret = devfreq_remove_governor(&devfreq_simple_usage); + if (ret) + pr_err("%s: failed remove governor %d\n", __func__, ret); + + return; +} +module_exit(devfreq_simple_usage_exit); +MODULE_LICENSE("GPL"); diff --git a/include/dt-bindings/soc/samsung/exynos9610-devfreq.h b/include/dt-bindings/soc/samsung/exynos9610-devfreq.h new file mode 100644 index 000000000000..b25726a3fcd9 --- /dev/null +++ b/include/dt-bindings/soc/samsung/exynos9610-devfreq.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd. + * + * 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. + * + * Device Tree binding constants for Exynos9610 devfreq + */ + +#ifndef _DT_BINDINGS_EXYNOS_9610_DEVFREQ_H +#define _DT_BINDINGS_EXYNOS_9610_DEVFREQ_H +/* DEVFREQ TYPE LIST */ +#define DEVFREQ_MIF 0 +#define DEVFREQ_INT 1 +#define DEVFREQ_DISP 2 +#define DEVFREQ_CAM 3 +#define DEVFREQ_INTCAM 4 +#define DEVFREQ_AUD 5 +#define DEVFREQ_TYPE_END 9 + +/* ESS FLAG LIST */ +#define ESS_FLAG_INT 2 +#define ESS_FLAG_MIF 3 +#define ESS_FLAG_ISP 4 +#define ESS_FLAG_DISP 5 +#define ESS_FLAG_INTCAM 6 +#define ESS_FLAG_AUD 7 + +/* DEVFREQ GOV TYPE */ +#define SIMPLE_INTERACTIVE 0 + +#endif diff --git a/include/linux/devfreq.h b/include/linux/devfreq.h index cca31812cc83..1b0c1059a5f9 100644 --- a/include/linux/devfreq.h +++ b/include/linux/devfreq.h @@ -16,6 +16,8 @@ #include #include #include +#include +#include #define DEVFREQ_NAME_LEN 16 @@ -46,8 +48,9 @@ struct devfreq_governor; */ struct devfreq_dev_status { /* both since the last measure */ - unsigned long total_time; - unsigned long busy_time; + unsigned long long total_time; + unsigned long long busy_time; + unsigned long long delta_time; unsigned long current_frequency; void *private_data; }; @@ -89,6 +92,7 @@ struct devfreq_dev_status { */ struct devfreq_dev_profile { unsigned long initial_freq; + unsigned long suspend_freq; unsigned int polling_ms; int (*target)(struct device *dev, unsigned long *freq, u32 flags); @@ -153,6 +157,7 @@ struct devfreq { unsigned long min_freq; unsigned long max_freq; + unsigned long str_freq; bool stop_polling; /* information for device frequency transition */ @@ -162,6 +167,8 @@ struct devfreq { unsigned long last_stat_updated; struct srcu_notifier_head transition_notifier_list; + + bool disabled_pm_qos; }; struct devfreq_freqs { @@ -214,6 +221,14 @@ extern void devm_devfreq_unregister_notifier(struct device *dev, extern struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev, int index); +#if IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND) || IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_USAGE)\ + || IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_INTERACTIVE) +struct devfreq_notifier_block { + struct notifier_block nb; + struct devfreq *df; +}; +#endif + #if IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND) /** * struct devfreq_simple_ondemand_data - void *data fed to struct devfreq @@ -229,6 +244,7 @@ extern struct devfreq *devfreq_get_devfreq_by_phandle(struct device *dev, * the governor uses the default values. */ struct devfreq_simple_ondemand_data { + unsigned int multiplication_weight; unsigned int upthreshold; unsigned int downdifferential; unsigned long cal_qos_max; @@ -267,6 +283,28 @@ struct devfreq_simple_exynos_data { }; #endif +#if IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_INTERACTIVE) +#define DEFAULT_DELAY_TIME 10 /* msec */ +#define DEFAULT_NDELAY_TIME 1 +#define DELAY_TIME_RANGE 10 +#define BOUND_CPU_NUM 0 + +struct devfreq_simple_interactive_data { + bool use_delay_time; + int *delay_time; + int ndelay_time; + unsigned long prev_freq; + u64 changed_time; + struct timer_list freq_timer; + struct timer_list freq_slack_timer; + struct task_struct *change_freq_task; + int pm_qos_class; + int pm_qos_class_max; + struct devfreq_notifier_block nb; + struct devfreq_notifier_block nb_max; +}; +#endif + #if IS_ENABLED(CONFIG_DEVFREQ_GOV_PASSIVE) /** * struct devfreq_passive_data - void *data fed to struct devfreq diff --git a/include/soc/samsung/exynos-devfreq.h b/include/soc/samsung/exynos-devfreq.h new file mode 100644 index 000000000000..77cdf95baeac --- /dev/null +++ b/include/soc/samsung/exynos-devfreq.h @@ -0,0 +1,112 @@ +/* linux/arch/arm64/mach-exynos/include/mach/exynos-devfreq.h + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * 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_DEVFREQ_H_ +#define __EXYNOS_DEVFREQ_H_ + +#include +#include +#include +#include +#ifdef CONFIG_EXYNOS_DVFS_MANAGER +#include +#endif + +#define EXYNOS_DEVFREQ_MODULE_NAME "exynos-devfreq" +#define VOLT_STEP 25000 +#define MAX_NR_CONSTRAINT DM_TYPE_END +#define DATA_INIT 5 +#define SET_CONST 1 +#define RELEASE 2 + +/* DEVFREQ GOV TYPE */ +#define SIMPLE_INTERACTIVE 0 + +struct exynos_devfreq_opp_table { + u32 idx; + u32 freq; + u32 volt; +}; + +struct exynos_devfreq_data { + struct device *dev; + struct devfreq *devfreq; + struct mutex lock; + struct clk *clk; + + bool devfreq_disabled; + + u32 devfreq_type; + + struct exynos_devfreq_opp_table *opp_list; + + u32 default_qos; + + u32 max_state; + struct devfreq_dev_profile devfreq_profile; + + u32 gov_type; + const char *governor_name; + u32 cal_qos_max; + void *governor_data; +#if IS_ENABLED(CONFIG_DEVFREQ_GOV_SIMPLE_INTERACTIVE) + struct devfreq_simple_interactive_data simple_interactive_data; +#endif + u32 dfs_id; + s32 old_idx; + s32 new_idx; + u32 old_freq; + u32 new_freq; + u32 min_freq; + u32 max_freq; + u32 reboot_freq; + u32 boot_freq; + + u32 old_volt; + u32 new_volt; + + u32 pm_qos_class; + u32 pm_qos_class_max; + struct pm_qos_request sys_pm_qos_min; +#ifdef CONFIG_ARM_EXYNOS_DEVFREQ_DEBUG + struct pm_qos_request debug_pm_qos_min; + struct pm_qos_request debug_pm_qos_max; +#endif + struct pm_qos_request default_pm_qos_min; + struct pm_qos_request default_pm_qos_max; + struct pm_qos_request boot_pm_qos; + u32 boot_qos_timeout; + + struct notifier_block reboot_notifier; + + u32 ess_flag; + + s32 target_delay; + +#ifdef CONFIG_EXYNOS_DVFS_MANAGER + u32 dm_type; + u32 nr_constraint; + struct exynos_dm_constraint **constraint; +#endif + void *private_data; + bool use_acpm; + bool bts_update; + bool update_fvp; + struct exynos_pm_domain *pm_domain; +}; + +s32 exynos_devfreq_get_opp_idx(struct exynos_devfreq_opp_table *table, + unsigned int size, u32 freq); +#if defined(CONFIG_ARM_EXYNOS_DEVFREQ) && defined(CONFIG_EXYNOS_DVFS_MANAGER) +u32 exynos_devfreq_get_dm_type(u32 devfreq_type); +u32 exynos_devfreq_get_devfreq_type(int dm_type); +struct device *find_exynos_devfreq_device(void *devdata); +int find_exynos_devfreq_dm_type(struct device *dev, int *dm_type); +#endif +#endif /* __EXYNOS_DEVFREQ_H_ */ -- 2.20.1