diff --git a/drivers/cpufreq/rockchip-cpufreq.c b/drivers/cpufreq/rockchip-cpufreq.c index 5de3d06c6efd..bcd8e59303f9 100644 --- a/drivers/cpufreq/rockchip-cpufreq.c +++ b/drivers/cpufreq/rockchip-cpufreq.c @@ -29,21 +29,14 @@ #include #include #include +#include #include "../clk/rockchip/clk.h" #define MAX_PROP_NAME_LEN 3 -#define LEAKAGE_TABLE_END ~1 #define LEAKAGE_INVALID 0xff - #define REBOOT_FREQ 816000 /* kHz */ -struct volt_sel_table { - int min; - int max; - int sel; -}; - struct cluster_info { struct list_head list_head; cpumask_t cpus; @@ -57,20 +50,6 @@ struct cluster_info { bool rebooting; bool freq_limit; }; - -struct pvtm_config { - unsigned int freq; - unsigned int volt; - unsigned int ch[2]; - unsigned int sample_time; - unsigned int num; - unsigned int err; - unsigned int ref_temp; - int temp_prop[2]; - const char *tz_name; - struct thermal_zone_device *tz; -}; - static LIST_HEAD(cluster_info_list); static struct cluster_info *rockchip_cluster_info_lookup(int cpu) @@ -138,204 +117,14 @@ static const struct of_device_id rockchip_cpufreq_of_match[] = { {}, }; -static int rockchip_get_volt_sel_table(struct device_node *np, char *porp_name, - struct volt_sel_table **table) -{ - struct volt_sel_table *sel_table; - const struct property *prop; - int count, i; - - prop = of_find_property(np, porp_name, NULL); - if (!prop) - return -EINVAL; - - if (!prop->value) - return -ENODATA; - - count = of_property_count_u32_elems(np, porp_name); - if (count < 0) - return -EINVAL; - - if (count % 3) - return -EINVAL; - - sel_table = kzalloc(sizeof(*sel_table) * (count / 3 + 1), GFP_KERNEL); - if (!sel_table) - return -ENOMEM; - - for (i = 0; i < count / 3; i++) { - of_property_read_u32_index(np, porp_name, 3 * i, - &sel_table[i].min); - of_property_read_u32_index(np, porp_name, 3 * i + 1, - &sel_table[i].max); - of_property_read_u32_index(np, porp_name, 3 * i + 2, - &sel_table[i].sel); - } - sel_table[i].min = 0; - sel_table[i].max = 0; - sel_table[i].sel = LEAKAGE_TABLE_END; - - *table = sel_table; - - return 0; -} - -static int rockchip_get_volt_sel(struct device_node *np, char *name, - int value, int *sel) -{ - struct volt_sel_table *table; - int i, j = -1, ret; - - ret = rockchip_get_volt_sel_table(np, name, &table); - if (ret) - return -EINVAL; - - for (i = 0; table[i].sel != LEAKAGE_TABLE_END; i++) { - if (value >= table[i].min) - j = i; - } - if (j != -1) - *sel = table[j].sel; - else - ret = -EINVAL; - - kfree(table); - - return ret; -} - -static int rockchip_parse_pvtm_config(struct device_node *np, - struct pvtm_config *pvtm) -{ - if (!of_find_property(np, "rockchip,pvtm-voltage-sel", NULL)) - return -EINVAL; - if (of_property_read_u32(np, "rockchip,pvtm-freq", &pvtm->freq)) - return -EINVAL; - if (of_property_read_u32(np, "rockchip,pvtm-volt", &pvtm->volt)) - return -EINVAL; - if (of_property_read_u32_array(np, "rockchip,pvtm-ch", pvtm->ch, 2)) - return -EINVAL; - if (of_property_read_u32(np, "rockchip,pvtm-sample-time", - &pvtm->sample_time)) - return -EINVAL; - if (of_property_read_u32(np, "rockchip,pvtm-number", &pvtm->num)) - return -EINVAL; - if (of_property_read_u32(np, "rockchip,pvtm-error", &pvtm->err)) - return -EINVAL; - if (of_property_read_u32(np, "rockchip,pvtm-ref-temp", &pvtm->ref_temp)) - return -EINVAL; - if (of_property_read_u32_array(np, "rockchip,pvtm-temp-prop", - pvtm->temp_prop, 2)) - return -EINVAL; - if (of_property_read_string(np, "rockchip,pvtm-thermal-zone", - &pvtm->tz_name)) - return -EINVAL; - pvtm->tz = thermal_zone_get_zone_by_name(pvtm->tz_name); - if (IS_ERR(pvtm->tz)) - return -EINVAL; - if (!pvtm->tz->ops->get_temp) - return -EINVAL; - - return 0; -} - -static int rockchip_get_pvtm_specific_value(struct device *dev, - struct device_node *np, - struct clk *clk, - struct regulator *reg, - int *target_value) -{ - struct pvtm_config *pvtm; - unsigned long old_freq; - unsigned int old_volt; - int cur_temp, diff_temp; - int cur_value, total_value, avg_value, diff_value; - int min_value, max_value; - int ret = 0, i = 0, retry = 2; - - pvtm = kzalloc(sizeof(*pvtm), GFP_KERNEL); - if (!pvtm) - return -ENOMEM; - - ret = rockchip_parse_pvtm_config(np, pvtm); - if (ret) - goto pvtm_value_out; - - old_freq = clk_get_rate(clk); - old_volt = regulator_get_voltage(reg); - - /* - * Set pvtm_freq to the lowest frequency in dts, - * so change frequency first. - */ - ret = clk_set_rate(clk, pvtm->freq * 1000); - if (ret) { - dev_err(dev, "Failed to set pvtm freq\n"); - goto pvtm_value_out; - } - - ret = regulator_set_voltage(reg, pvtm->volt, pvtm->volt); - if (ret) { - dev_err(dev, "Failed to set pvtm_volt\n"); - goto restore_clk; - } - - /* The first few values may be fluctuant, if error is too big, retry*/ - while (retry--) { - total_value = 0; - min_value = INT_MAX; - max_value = 0; - - for (i = 0; i < pvtm->num; i++) { - cur_value = rockchip_get_pvtm_value(pvtm->ch[0], - pvtm->ch[1], - pvtm->sample_time); - if (!cur_value) - goto resetore_volt; - if (cur_value < min_value) - min_value = cur_value; - if (cur_value > max_value) - max_value = cur_value; - total_value += cur_value; - } - if (max_value - min_value < pvtm->err) - break; - } - avg_value = total_value / pvtm->num; - - /* - * As pvtm is influenced by temperature, compute difference between - * current temperature and reference temperature - */ - pvtm->tz->ops->get_temp(pvtm->tz, &cur_temp); - diff_temp = (cur_temp / 1000 - pvtm->ref_temp); - diff_value = diff_temp * - (diff_temp < 0 ? pvtm->temp_prop[0] : pvtm->temp_prop[1]); - *target_value = avg_value + diff_value; - - dev_info(dev, "temp=%d, pvtm=%d (%d + %d)\n", - cur_temp, *target_value, avg_value, diff_value); - -resetore_volt: - regulator_set_voltage(reg, old_volt, old_volt); -restore_clk: - clk_set_rate(clk, old_freq); -pvtm_value_out: - kfree(pvtm); - - return ret; -} - static int rockchip_cpufreq_cluster_init(int cpu, struct cluster_info *cluster) { int (*get_soc_version)(struct device_node *np, int *soc_version); const struct of_device_id *match; struct device_node *node, *np; struct clk *clk; - struct clk *cpu_clk; - struct regulator *cpu_reg; struct device *dev; - int lkg_volt_sel = -1, pvtm_volt_sel = -1, lkg_scale_sel = -1; + int lkg_volt_sel, pvtm_volt_sel, lkg_scale_sel; int ret; dev = get_cpu_device(cpu); @@ -371,63 +160,25 @@ static int rockchip_cpufreq_cluster_init(int cpu, struct cluster_info *cluster) &cluster->threshold_freq); cluster->freq_limit = of_property_read_bool(np, "rockchip,freq-limit"); - cluster->volt_sel = -1; - ret = rockchip_get_efuse_value(np, "cpu_leakage", &cluster->leakage); - if (!ret) { - dev_info(dev, "leakage=%d\n", cluster->leakage); - ret = rockchip_get_volt_sel(np, "rockchip,leakage-voltage-sel", - cluster->leakage, &lkg_volt_sel); - if (!ret) { - dev_info(dev, "leakage-sel=%d\n", lkg_volt_sel); - cluster->volt_sel = lkg_volt_sel; + lkg_scale_sel = rockchip_of_get_lkg_scale_sel(dev, "cpu_leakage"); + if (lkg_scale_sel > 0) { + clk = of_clk_get_by_name(np, NULL); + if (IS_ERR(clk)) { + dev_err(dev, "Failed to get opp clk"); + return PTR_ERR(clk); } - - ret = rockchip_get_volt_sel(np, "rockchip,leakage-scaling-sel", - cluster->leakage, - &lkg_scale_sel); - if (!ret) { - dev_info(dev, "scale-sel=%d\n", lkg_scale_sel); - clk = of_clk_get_by_name(np, NULL); - if (IS_ERR(clk)) { - dev_err(dev, "Failed to get opp clk"); - return PTR_ERR(clk); - } - ret = rockchip_pll_clk_adaptive_scaling(clk, - lkg_scale_sel); - if (ret) { - dev_err(dev, "Failed to adaptive scaling\n"); - return ret; - } + ret = rockchip_pll_clk_adaptive_scaling(clk, + lkg_scale_sel); + if (ret) { + dev_err(dev, "Failed to adaptive scaling\n"); + return ret; } } - cpu_clk = clk_get(dev, NULL); - if (IS_ERR_OR_NULL(cpu_clk)) { - dev_err(dev, "Failed to get clk\n"); - goto parse_dt_out; - } - cpu_reg = regulator_get_optional(dev, "cpu"); - if (IS_ERR_OR_NULL(cpu_reg)) { - dev_err(dev, "Failed to get cpu_reg\n"); - goto put_clk; - } - ret = rockchip_get_pvtm_specific_value(dev, np, cpu_clk, cpu_reg, - &cluster->pvtm); - if (!ret) { - ret = rockchip_get_volt_sel(np, "rockchip,pvtm-voltage-sel", - cluster->pvtm, &pvtm_volt_sel); - if (!ret) { - dev_info(dev, "pvtm-sel=%d\n", pvtm_volt_sel); - if (cluster->volt_sel < 0 || - cluster->volt_sel > pvtm_volt_sel) - cluster->volt_sel = pvtm_volt_sel; - } - } + lkg_volt_sel = rockchip_of_get_lkg_volt_sel(dev, "cpu_leakage"); + pvtm_volt_sel = rockchip_of_get_pvtm_volt_sel(dev, NULL, "cpu"); - regulator_put(cpu_reg); -put_clk: - clk_put(cpu_clk); -parse_dt_out: + cluster->volt_sel = max(lkg_volt_sel, pvtm_volt_sel); return 0; } diff --git a/drivers/devfreq/rockchip_dmc.c b/drivers/devfreq/rockchip_dmc.c index e0e84dee47c4..05dd77ebbca8 100644 --- a/drivers/devfreq/rockchip_dmc.c +++ b/drivers/devfreq/rockchip_dmc.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -2347,6 +2348,9 @@ static int rockchip_dmcfreq_probe(struct platform_device *pdev) const struct of_device_id *match; int (*init)(struct platform_device *pdev, struct rockchip_dmcfreq *data); +#define MAX_PROP_NAME_LEN 3 + char name[MAX_PROP_NAME_LEN]; + int lkg_volt_sel; int ret; data = devm_kzalloc(dev, sizeof(struct rockchip_dmcfreq), GFP_KERNEL); @@ -2392,6 +2396,14 @@ static int rockchip_dmcfreq_probe(struct platform_device *pdev) * We add a devfreq driver to our parent since it has a device tree node * with operating points. */ + lkg_volt_sel = rockchip_of_get_lkg_volt_sel(dev, "ddr_leakage"); + if (lkg_volt_sel >= 0) { + snprintf(name, MAX_PROP_NAME_LEN, "L%d", lkg_volt_sel); + ret = dev_pm_opp_set_prop_name(dev, name); + if (ret) + dev_err(dev, "Failed to set prop name\n"); + } + if (dev_pm_opp_of_add_table(dev)) { dev_err(dev, "Invalid operating-points in device tree.\n"); return -EINVAL; diff --git a/drivers/gpu/arm/mali400/mali/linux/mali_kernel_linux.c b/drivers/gpu/arm/mali400/mali/linux/mali_kernel_linux.c index ff3e5864e2ad..ecfd5c3456bd 100755 --- a/drivers/gpu/arm/mali400/mali/linux/mali_kernel_linux.c +++ b/drivers/gpu/arm/mali400/mali/linux/mali_kernel_linux.c @@ -30,8 +30,9 @@ #include #include #include - #include +#include + #include "mali_kernel_common.h" #include "mali_session.h" #include "mali_kernel_core.h" @@ -517,7 +518,10 @@ static int mali_probe(struct platform_device *pdev) { int err; #ifdef CONFIG_MALI_DEVFREQ +#define MAX_PROP_NAME_LEN 3 struct mali_device *mdev; + char name[MAX_PROP_NAME_LEN]; + int lkg_volt_sel; #endif MALI_DEBUG_PRINT(2, ("mali_probe(): Called for platform device %s\n", pdev->name)); @@ -563,6 +567,14 @@ static int mali_probe(struct platform_device *pdev) mdev->dev = &pdev->dev; dev_set_drvdata(mdev->dev, mdev); + lkg_volt_sel = rockchip_of_get_lkg_volt_sel(mdev->dev, "gpu_leakage"); + if (lkg_volt_sel >= 0) { + snprintf(name, MAX_PROP_NAME_LEN, "L%d", lkg_volt_sel); + err = dev_pm_opp_set_prop_name(mdev->dev, name); + if (err) + dev_err(mdev->dev, "Failed to set prop name\n"); + } + /*Initilization clock and regulator*/ #if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)) && defined(CONFIG_OF) \ && defined(CONFIG_REGULATOR) diff --git a/drivers/gpu/arm/midgard/platform/rk/mali_kbase_config_rk.c b/drivers/gpu/arm/midgard/platform/rk/mali_kbase_config_rk.c index da3c47b0bb90..de986153f40d 100644 --- a/drivers/gpu/arm/midgard/platform/rk/mali_kbase_config_rk.c +++ b/drivers/gpu/arm/midgard/platform/rk/mali_kbase_config_rk.c @@ -23,32 +23,10 @@ #include #include #include +#include #include "mali_kbase_rk.h" -#define MAX_PROP_NAME_LEN 3 -#define LEAKAGE_TABLE_END ~1 -#define LEAKAGE_INVALID 0xff - -struct pvtm_config { - unsigned int freq; - unsigned int volt; - unsigned int ch[2]; - unsigned int sample_time; - unsigned int num; - unsigned int err; - unsigned int ref_temp; - int temp_prop[2]; - const char *tz_name; - struct thermal_zone_device *tz; -}; - -struct volt_sel_table { - int min; - int max; - int sel; -}; - /** * @file mali_kbase_config_rk.c * 对 platform_config_of_rk 的具体实现. @@ -451,268 +429,20 @@ static void kbase_platform_rk_remove_sysfs_files(struct device *dev) device_remove_file(dev, &dev_attr_utilisation); } -static int rockchip_get_efuse_value(struct device_node *np, char *porp_name, - int *value) -{ - struct nvmem_cell *cell; - unsigned char *buf; - size_t len; - - cell = of_nvmem_cell_get(np, porp_name); - if (IS_ERR(cell)) - return PTR_ERR(cell); - - buf = (unsigned char *)nvmem_cell_read(cell, &len); - - nvmem_cell_put(cell); - - if (IS_ERR(buf)) - return PTR_ERR(buf); - - if (buf[0] == LEAKAGE_INVALID) - return -EINVAL; - - *value = buf[0]; - - kfree(buf); - - return 0; -} - -static int rockchip_get_volt_sel_table(struct device_node *np, char *porp_name, - struct volt_sel_table **table) -{ - struct volt_sel_table *sel_table; - const struct property *prop; - int count, i; - - prop = of_find_property(np, porp_name, NULL); - if (!prop) - return -EINVAL; - - if (!prop->value) - return -ENODATA; - - count = of_property_count_u32_elems(np, porp_name); - if (count < 0) - return -EINVAL; - - if (count % 3) - return -EINVAL; - - sel_table = kzalloc(sizeof(*sel_table) * (count / 3 + 1), GFP_KERNEL); - if (!sel_table) - return -ENOMEM; - - for (i = 0; i < count / 3; i++) { - of_property_read_u32_index(np, porp_name, 3 * i, - &sel_table[i].min); - of_property_read_u32_index(np, porp_name, 3 * i + 1, - &sel_table[i].max); - of_property_read_u32_index(np, porp_name, 3 * i + 2, - &sel_table[i].sel); - } - sel_table[i].min = 0; - sel_table[i].max = 0; - sel_table[i].sel = LEAKAGE_TABLE_END; - - *table = sel_table; - - return 0; -} - -static int rockchip_get_volt_sel(struct device_node *np, char *name, - int value, int *sel) -{ - struct volt_sel_table *table; - int i, j = -1, ret; - - ret = rockchip_get_volt_sel_table(np, name, &table); - if (ret) - return -EINVAL; - - for (i = 0; table[i].sel != LEAKAGE_TABLE_END; i++) { - if (value >= table[i].min) - j = i; - } - if (j != -1) - *sel = table[j].sel; - else - ret = -EINVAL; - - kfree(table); - - return ret; -} - -static int rockchip_parse_pvtm_config(struct device_node *np, - struct pvtm_config *pvtm) -{ - if (!of_find_property(np, "rockchip,pvtm-voltage-sel", NULL)) - return -EINVAL; - if (of_property_read_u32(np, "rockchip,pvtm-freq", &pvtm->freq)) - return -EINVAL; - if (of_property_read_u32(np, "rockchip,pvtm-volt", &pvtm->volt)) - return -EINVAL; - if (of_property_read_u32_array(np, "rockchip,pvtm-ch", pvtm->ch, 2)) - return -EINVAL; - if (of_property_read_u32(np, "rockchip,pvtm-sample-time", - &pvtm->sample_time)) - return -EINVAL; - if (of_property_read_u32(np, "rockchip,pvtm-number", &pvtm->num)) - return -EINVAL; - if (of_property_read_u32(np, "rockchip,pvtm-error", &pvtm->err)) - return -EINVAL; - if (of_property_read_u32(np, "rockchip,pvtm-ref-temp", &pvtm->ref_temp)) - return -EINVAL; - if (of_property_read_u32_array(np, "rockchip,pvtm-temp-prop", - pvtm->temp_prop, 2)) - return -EINVAL; - if (of_property_read_string(np, "rockchip,pvtm-thermal-zone", - &pvtm->tz_name)) - return -EINVAL; - pvtm->tz = thermal_zone_get_zone_by_name(pvtm->tz_name); - if (IS_ERR(pvtm->tz)) - return -EINVAL; - if (!pvtm->tz->ops->get_temp) - return -EINVAL; - - return 0; -} - -static int rockchip_get_pvtm_specific_value(struct device *dev, - struct device_node *np, - struct clk *clk, - struct regulator *reg, - int *target_value) -{ - struct pvtm_config *pvtm; - unsigned long old_freq; - unsigned int old_volt; - int cur_temp, diff_temp; - int cur_value, total_value, avg_value, diff_value; - int min_value, max_value; - int ret = 0, i = 0, retry = 2; - - pvtm = kzalloc(sizeof(*pvtm), GFP_KERNEL); - if (!pvtm) - return -ENOMEM; - - ret = rockchip_parse_pvtm_config(np, pvtm); - if (ret) - goto pvtm_value_out; - - old_freq = clk_get_rate(clk); - old_volt = regulator_get_voltage(reg); - - /* - * Set pvtm_freq to the lowest frequency in dts, - * so change frequency first. - */ - ret = clk_set_rate(clk, pvtm->freq * 1000); - if (ret) { - dev_err(dev, "Failed to set pvtm freq\n"); - goto pvtm_value_out; - } - - ret = regulator_set_voltage(reg, pvtm->volt, pvtm->volt); - if (ret) { - dev_err(dev, "Failed to set pvtm_volt\n"); - goto restore_clk; - } - - /* The first few values may be fluctuant, if error is too big, retry*/ - while (retry--) { - total_value = 0; - min_value = INT_MAX; - max_value = 0; - - for (i = 0; i < pvtm->num; i++) { - cur_value = rockchip_get_pvtm_value(pvtm->ch[0], - pvtm->ch[1], - pvtm->sample_time); - if (!cur_value) - goto resetore_volt; - if (cur_value < min_value) - min_value = cur_value; - if (cur_value > max_value) - max_value = cur_value; - total_value += cur_value; - } - if (max_value - min_value < pvtm->err) - break; - } - avg_value = total_value / pvtm->num; - - /* - * As pvtm is influenced by temperature, compute difference between - * current temperature and reference temperature - */ - pvtm->tz->ops->get_temp(pvtm->tz, &cur_temp); - diff_temp = (cur_temp / 1000 - pvtm->ref_temp); - diff_value = diff_temp * - (diff_temp < 0 ? pvtm->temp_prop[0] : pvtm->temp_prop[1]); - *target_value = avg_value + diff_value; - - dev_info(dev, "temp=%d, pvtm=%d (%d + %d)\n", - cur_temp, *target_value, avg_value, diff_value); - -resetore_volt: - regulator_set_voltage(reg, old_volt, old_volt); -restore_clk: - clk_set_rate(clk, old_freq); -pvtm_value_out: - kfree(pvtm); - - return ret; -} - void kbase_platform_rk_set_opp_info(struct kbase_device *kbdev) { - struct device_node *np; +#define MAX_PROP_NAME_LEN 3 char name[MAX_PROP_NAME_LEN]; - int pvmt_value, leakage; - int lkg_volt_sel, pvtm_volt_sel, volt_sel = -1; + int lkg_volt_sel, pvtm_volt_sel, volt_sel; int err = 0; if (!kbdev) return; - if (IS_ERR_OR_NULL(kbdev->regulator)) - return; - if (IS_ERR_OR_NULL(kbdev->clock)) - return; - - np = of_parse_phandle(kbdev->dev->of_node, "operating-points-v2", 0); - if (!np) { - dev_warn(kbdev->dev, "OPP-v2 not supported\n"); - return; - } - - err = rockchip_get_efuse_value(np, "gpu_leakage", &leakage); - if (!err) { - dev_info(kbdev->dev, "leakage=%d\n", leakage); - err = rockchip_get_volt_sel(np, "rockchip,leakage-voltage-sel", - leakage, &lkg_volt_sel); - if (!err) { - dev_info(kbdev->dev, "leakage-sel=%d\n", lkg_volt_sel); - volt_sel = lkg_volt_sel; - } - } - - err = rockchip_get_pvtm_specific_value(kbdev->dev, np, kbdev->clock, - kbdev->regulator, - &pvmt_value); - if (!err) { - err = rockchip_get_volt_sel(np, "rockchip,pvtm-voltage-sel", - pvmt_value, &pvtm_volt_sel); - if (!err) { - dev_info(kbdev->dev, "pvtm-sel=%d\n", pvtm_volt_sel); - if (volt_sel < 0 || volt_sel > pvtm_volt_sel) - volt_sel = pvtm_volt_sel; - } - } + lkg_volt_sel = rockchip_of_get_lkg_volt_sel(kbdev->dev, "gpu_leakage"); + pvtm_volt_sel = rockchip_of_get_pvtm_volt_sel(kbdev->dev, NULL, "mali"); + volt_sel = max(lkg_volt_sel, pvtm_volt_sel); if (volt_sel >= 0) { snprintf(name, MAX_PROP_NAME_LEN, "L%d", volt_sel); err = dev_pm_opp_set_prop_name(kbdev->dev, name); diff --git a/drivers/soc/rockchip/Makefile b/drivers/soc/rockchip/Makefile index 57729e25992e..f7697e7b334d 100644 --- a/drivers/soc/rockchip/Makefile +++ b/drivers/soc/rockchip/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_ROCK_CHIP_SOC_CAMERA) += rk_camera.o obj-y += rk_vendor_storage.o obj-y += rockchip-system-status.o +obj-y += rockchip_opp_select.o diff --git a/drivers/soc/rockchip/rockchip_opp_select.c b/drivers/soc/rockchip/rockchip_opp_select.c new file mode 100644 index 000000000000..e1a53c85b067 --- /dev/null +++ b/drivers/soc/rockchip/rockchip_opp_select.c @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2017 Fuzhou Rockchip Electronics Co., Ltd + * + * SPDX-License-Identifier: GPL-2.0+ + */ +#include +#include +#include +#include +#include +#include +#include + +#define LEAKAGE_TABLE_END ~1 +#define LEAKAGE_INVALID 0xff + +struct volt_sel_table { + int min; + int max; + int sel; +}; + +struct pvtm_config { + unsigned int freq; + unsigned int volt; + unsigned int ch[2]; + unsigned int sample_time; + unsigned int num; + unsigned int err; + unsigned int ref_temp; + int temp_prop[2]; + const char *tz_name; + struct thermal_zone_device *tz; +}; + +static int rockchip_get_efuse_value(struct device_node *np, char *porp_name, + int *value) +{ + struct nvmem_cell *cell; + unsigned char *buf; + size_t len; + + cell = of_nvmem_cell_get(np, porp_name); + if (IS_ERR(cell)) + return PTR_ERR(cell); + + buf = (unsigned char *)nvmem_cell_read(cell, &len); + + nvmem_cell_put(cell); + + if (IS_ERR(buf)) + return PTR_ERR(buf); + + if (buf[0] == LEAKAGE_INVALID) + return -EINVAL; + + *value = buf[0]; + + kfree(buf); + + return 0; +} + +static int rockchip_get_volt_sel_table(struct device_node *np, char *porp_name, + struct volt_sel_table **table) +{ + struct volt_sel_table *sel_table; + const struct property *prop; + int count, i; + + prop = of_find_property(np, porp_name, NULL); + if (!prop) + return -EINVAL; + + if (!prop->value) + return -ENODATA; + + count = of_property_count_u32_elems(np, porp_name); + if (count < 0) + return -EINVAL; + + if (count % 3) + return -EINVAL; + + sel_table = kzalloc(sizeof(*sel_table) * (count / 3 + 1), GFP_KERNEL); + if (!sel_table) + return -ENOMEM; + + for (i = 0; i < count / 3; i++) { + of_property_read_u32_index(np, porp_name, 3 * i, + &sel_table[i].min); + of_property_read_u32_index(np, porp_name, 3 * i + 1, + &sel_table[i].max); + of_property_read_u32_index(np, porp_name, 3 * i + 2, + &sel_table[i].sel); + } + sel_table[i].min = 0; + sel_table[i].max = 0; + sel_table[i].sel = LEAKAGE_TABLE_END; + + *table = sel_table; + + return 0; +} + +static int rockchip_get_volt_sel(struct device_node *np, char *name, + int value, int *sel) +{ + struct volt_sel_table *table; + int i, j = -1, ret; + + ret = rockchip_get_volt_sel_table(np, name, &table); + if (ret) + return -EINVAL; + + for (i = 0; table[i].sel != LEAKAGE_TABLE_END; i++) { + if (value >= table[i].min) + j = i; + } + if (j != -1) + *sel = table[j].sel; + else + ret = -EINVAL; + + kfree(table); + + return ret; +} + +static int rockchip_parse_pvtm_config(struct device_node *np, + struct pvtm_config *pvtm) +{ + if (!of_find_property(np, "rockchip,pvtm-voltage-sel", NULL)) + return -EINVAL; + if (of_property_read_u32(np, "rockchip,pvtm-freq", &pvtm->freq)) + return -EINVAL; + if (of_property_read_u32(np, "rockchip,pvtm-volt", &pvtm->volt)) + return -EINVAL; + if (of_property_read_u32_array(np, "rockchip,pvtm-ch", pvtm->ch, 2)) + return -EINVAL; + if (of_property_read_u32(np, "rockchip,pvtm-sample-time", + &pvtm->sample_time)) + return -EINVAL; + if (of_property_read_u32(np, "rockchip,pvtm-number", &pvtm->num)) + return -EINVAL; + if (of_property_read_u32(np, "rockchip,pvtm-error", &pvtm->err)) + return -EINVAL; + if (of_property_read_u32(np, "rockchip,pvtm-ref-temp", &pvtm->ref_temp)) + return -EINVAL; + if (of_property_read_u32_array(np, "rockchip,pvtm-temp-prop", + pvtm->temp_prop, 2)) + return -EINVAL; + if (of_property_read_string(np, "rockchip,pvtm-thermal-zone", + &pvtm->tz_name)) + return -EINVAL; + pvtm->tz = thermal_zone_get_zone_by_name(pvtm->tz_name); + if (IS_ERR(pvtm->tz)) + return -EINVAL; + if (!pvtm->tz->ops->get_temp) + return -EINVAL; + + return 0; +} + +static int rockchip_get_pvtm_specific_value(struct device *dev, + struct device_node *np, + struct clk *clk, + struct regulator *reg, + int *target_value) +{ + struct pvtm_config *pvtm; + unsigned long old_freq; + unsigned int old_volt; + int cur_temp, diff_temp; + int cur_value, total_value, avg_value, diff_value; + int min_value, max_value; + int ret = 0, i = 0, retry = 2; + + pvtm = kzalloc(sizeof(*pvtm), GFP_KERNEL); + if (!pvtm) + return -ENOMEM; + + ret = rockchip_parse_pvtm_config(np, pvtm); + if (ret) + goto pvtm_value_out; + + old_freq = clk_get_rate(clk); + old_volt = regulator_get_voltage(reg); + + /* + * Set pvtm_freq to the lowest frequency in dts, + * so change frequency first. + */ + ret = clk_set_rate(clk, pvtm->freq * 1000); + if (ret) { + dev_err(dev, "Failed to set pvtm freq\n"); + goto pvtm_value_out; + } + + ret = regulator_set_voltage(reg, pvtm->volt, pvtm->volt); + if (ret) { + dev_err(dev, "Failed to set pvtm_volt\n"); + goto restore_clk; + } + + /* The first few values may be fluctuant, if error is too big, retry*/ + while (retry--) { + total_value = 0; + min_value = INT_MAX; + max_value = 0; + + for (i = 0; i < pvtm->num; i++) { + cur_value = rockchip_get_pvtm_value(pvtm->ch[0], + pvtm->ch[1], + pvtm->sample_time); + if (!cur_value) + goto resetore_volt; + if (cur_value < min_value) + min_value = cur_value; + if (cur_value > max_value) + max_value = cur_value; + total_value += cur_value; + } + if (max_value - min_value < pvtm->err) + break; + } + avg_value = total_value / pvtm->num; + + /* + * As pvtm is influenced by temperature, compute difference between + * current temperature and reference temperature + */ + pvtm->tz->ops->get_temp(pvtm->tz, &cur_temp); + diff_temp = (cur_temp / 1000 - pvtm->ref_temp); + diff_value = diff_temp * + (diff_temp < 0 ? pvtm->temp_prop[0] : pvtm->temp_prop[1]); + *target_value = avg_value + diff_value; + + dev_info(dev, "temp=%d, pvtm=%d (%d + %d)\n", + cur_temp, *target_value, avg_value, diff_value); + +resetore_volt: + regulator_set_voltage(reg, old_volt, old_volt); +restore_clk: + clk_set_rate(clk, old_freq); +pvtm_value_out: + kfree(pvtm); + + return ret; +} + +int rockchip_of_get_lkg_scale_sel(struct device *dev, char *name) +{ + struct device_node *np; + int leakage, volt_sel; + int ret; + + np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); + if (!np) { + dev_warn(dev, "OPP-v2 not supported\n"); + return -ENOENT; + } + + ret = rockchip_get_efuse_value(np, name, &leakage); + if (!ret) { + dev_info(dev, "%s=%d\n", name, leakage); + ret = rockchip_get_volt_sel(np, "rockchip,leakage-scaling-sel", + leakage, &volt_sel); + if (!ret) { + dev_info(dev, "%s-scale-sel=%d\n", name, volt_sel); + return volt_sel; + } + } + + return ret; +} + +int rockchip_of_get_lkg_volt_sel(struct device *dev, char *name) +{ + struct device_node *np; + int leakage, volt_sel; + int ret; + + np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); + if (!np) { + dev_warn(dev, "OPP-v2 not supported\n"); + return -ENOENT; + } + + ret = rockchip_get_efuse_value(np, name, &leakage); + if (!ret) { + dev_info(dev, "%s=%d\n", name, leakage); + ret = rockchip_get_volt_sel(np, "rockchip,leakage-voltage-sel", + leakage, &volt_sel); + if (!ret) { + dev_info(dev, "%s-volt-sel=%d\n", name, volt_sel); + return volt_sel; + } + } + + return ret; +} + +int rockchip_of_get_pvtm_volt_sel(struct device *dev, + char *clk_name, + char *reg_name) +{ + struct device_node *np; + struct regulator *reg; + struct clk *clk; + int pvtm, volt_sel; + int ret; + + np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); + if (!np) { + dev_warn(dev, "OPP-v2 not supported\n"); + return -ENOENT; + } + + clk = clk_get(dev, clk_name); + if (IS_ERR_OR_NULL(clk)) { + dev_err(dev, "Failed to get clk\n"); + return PTR_ERR(clk); + } + + reg = regulator_get_optional(dev, reg_name); + if (IS_ERR_OR_NULL(reg)) { + dev_err(dev, "Failed to get reg\n"); + clk_put(clk); + return PTR_ERR(reg); + } + + ret = rockchip_get_pvtm_specific_value(dev, np, clk, reg, &pvtm); + if (!ret) + ret = rockchip_get_volt_sel(np, "rockchip,pvtm-voltage-sel", + pvtm, &volt_sel); + + regulator_put(reg); + clk_put(clk); + + return ret ? ret : volt_sel; +} + diff --git a/include/soc/rockchip/rockchip_opp_select.h b/include/soc/rockchip/rockchip_opp_select.h new file mode 100644 index 000000000000..4e0b2e12da54 --- /dev/null +++ b/include/soc/rockchip/rockchip_opp_select.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2017 Fuzhou Rockchip Electronics Co., Ltd + * + * SPDX-License-Identifier: GPL-2.0+ + */ +#ifndef __SOC_ROCKCHIP_OPP_SELECT_H +#define __SOC_ROCKCHIP_OPP_SELECT_H + +int rockchip_of_get_lkg_scale_sel(struct device *dev, char *name); +int rockchip_of_get_lkg_volt_sel(struct device *dev, char *name); +int rockchip_of_get_pvtm_volt_sel(struct device *dev, + char *clk_name, + char *reg_name); + +#endif