From 92e3b22798304cc2eeaa42c2d4f9eacb50125fae Mon Sep 17 00:00:00 2001 From: Finley Xiao Date: Wed, 12 Apr 2017 12:31:23 +0800 Subject: [PATCH] cpufreq: rockchip: Provide runtime initialised driver This path introduces a rockchip-cpufreq driver, which can determine available OPPs and select a suitable voltage for available OPPs according to SoC version and leakage valuses in eFuse at runtime. If all cpus of a cluster are downed, opp table will be removed, prop-name and supported_hw are noneffective. So add a hotcpu notifier to set them again when a cpu of the closed cluster is upped. Change-Id: I43ab3e2cad4a9fefd5be5b0596cd841c392d7a8b Signed-off-by: Finley Xiao --- drivers/cpufreq/rockchip-cpufreq.c | 355 ++++++++++++++++++++++++++++- 1 file changed, 351 insertions(+), 4 deletions(-) diff --git a/drivers/cpufreq/rockchip-cpufreq.c b/drivers/cpufreq/rockchip-cpufreq.c index ba43a69bc1d8..c2f7b1c61319 100644 --- a/drivers/cpufreq/rockchip-cpufreq.c +++ b/drivers/cpufreq/rockchip-cpufreq.c @@ -1,8 +1,7 @@ /* + * Rockchip CPUFreq Driver * - * Copyright (C) 2015 Fuzhou Rockchip Electronics Co., Ltd - * - * Xiao Feng + * Copyright (C) 2017 Fuzhou Rockchip 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 @@ -14,23 +13,371 @@ * GNU General Public License for more details. */ +#include #include #include #include #include +#include #include #include +#include +#include + +#define MAX_CLUSTERS 2 +#define MAX_PROP_NAME_LEN 3 +#define LEAKAGE_TABLE_END ~1 +#define INVALID_VALUE 0xff + +struct leakage_table { + int min; + int max; + int value; +}; + +struct cluster_info { + cpumask_t cpus; + int leakage; + int lkg_volt_sel; + int soc_version; + bool set_opp; +}; + +struct rockchip_cpufreq_data { + struct notifier_block hotcpu_notify; + struct cluster_info *cluster; +}; + +#define hotcpu_notify_to_avs(_n) container_of(_n, \ + struct rockchip_cpufreq_data, \ + hotcpu_notify) + +static int rockchip_efuse_get_one_byte(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] == INVALID_VALUE) + return -EINVAL; + + *value = buf[0]; + + kfree(buf); + + return 0; +} + +static int rk3399_get_soc_version(struct device_node *np, int *soc_version) +{ + int ret, version; + + if (of_property_match_string(np, "nvmem-cell-names", + "soc_version") < 0) + return 0; + + ret = rockchip_efuse_get_one_byte(np, "soc_version", + &version); + if (ret) + return ret; + + *soc_version = (version & 0xf0) >> 4; + + return 0; +} + +static const struct of_device_id rockchip_cpufreq_of_match[] = { + { + .compatible = "rockchip,rk3399", + .data = (void *)&rk3399_get_soc_version, + }, + {}, +}; + +static int rockchip_get_leakage_table(struct device_node *np, char *porp_name, + struct leakage_table **table) +{ + struct leakage_table *lkg_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; + + lkg_table = kzalloc(sizeof(*lkg_table) * (count / 3 + 1), GFP_KERNEL); + if (!lkg_table) + return -ENOMEM; + + for (i = 0; i < count / 3; i++) { + of_property_read_u32_index(np, porp_name, 3 * i, + &lkg_table[i].min); + of_property_read_u32_index(np, porp_name, 3 * i + 1, + &lkg_table[i].max); + of_property_read_u32_index(np, porp_name, 3 * i + 2, + &lkg_table[i].value); + } + lkg_table[i].min = 0; + lkg_table[i].max = 0; + lkg_table[i].value = LEAKAGE_TABLE_END; + + *table = lkg_table; + + return 0; +} + +static int rockchip_get_leakage_sel(struct device_node *np, char *name, + int leakage, int *value) +{ + struct leakage_table *table; + struct property *prop; + int i, j = -1, ret; + + if (of_property_match_string(np, "nvmem-cell-names", "cpu_leakage") < 0) + return 0; + + prop = of_find_property(np, name, NULL); + if (!prop) + return 0; + + ret = rockchip_get_leakage_table(np, name, &table); + if (ret) + return -EINVAL; + + for (i = 0; table[i].value != LEAKAGE_TABLE_END; i++) { + if (leakage >= table[i].min) + j = i; + } + if (j != -1) + *value = table[j].value; + else + ret = -EINVAL; + + kfree(table); + + return ret; +} + +static int rockchip_cpufreq_of_parse_dt(struct device *dev, + 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; + int ret; + + np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); + if (!np) { + dev_info(dev, "OPP-v2 not supported\n"); + return -EINVAL; + } + + cluster->soc_version = -1; + node = of_find_node_by_path("/"); + match = of_match_node(rockchip_cpufreq_of_match, node); + if (match && match->data) { + get_soc_version = match->data; + ret = get_soc_version(np, &cluster->soc_version); + if (ret) { + dev_err(dev, "Failed to get chip_version\n"); + return ret; + } + } + + ret = rockchip_efuse_get_one_byte(np, "cpu_leakage", &cluster->leakage); + if (ret) + dev_err(dev, "Failed to get cpu_leakage\n"); + else + dev_info(dev, "leakage=%d\n", cluster->leakage); + + cluster->lkg_volt_sel = -1; + ret = rockchip_get_leakage_sel(np, "leakage-voltage-sel", + cluster->leakage, + &cluster->lkg_volt_sel); + if (ret) { + dev_err(dev, "Failed to get voltage-sel\n"); + return ret; + } + + return 0; +} + +static int rockchip_cpufreq_set_opp_info(struct device *dev, + struct cluster_info *cluster) +{ + char name[MAX_PROP_NAME_LEN]; + int ret, version; + + if (cluster->soc_version != -1 && cluster->lkg_volt_sel != -1) + snprintf(name, MAX_PROP_NAME_LEN, "S%d-L%d", + cluster->soc_version, + cluster->lkg_volt_sel); + else if (cluster->soc_version != -1 && cluster->lkg_volt_sel == -1) + snprintf(name, MAX_PROP_NAME_LEN, "S%d", cluster->soc_version); + else if (cluster->soc_version == -1 && cluster->lkg_volt_sel != -1) + snprintf(name, MAX_PROP_NAME_LEN, "L%d", cluster->lkg_volt_sel); + else + return -EINVAL; + + ret = dev_pm_opp_set_prop_name(dev, name); + if (ret) { + dev_err(dev, "Failed to set prop name\n"); + return ret; + } + + if (cluster->soc_version != -1) { + version = BIT(cluster->soc_version); + ret = dev_pm_opp_set_supported_hw(dev, &version, 1); + if (ret) { + dev_err(dev, "Failed to set supported hardware\n"); + return ret; + } + } + + return 0; +} + +static int rockchip_hotcpu_notifier(struct notifier_block *nb, + unsigned long action, void *hcpu) +{ + struct rockchip_cpufreq_data *data = hotcpu_notify_to_avs(nb); + unsigned int cpu = (unsigned long)hcpu; + struct cluster_info *cluster; + struct device *dev; + cpumask_t cpus; + int i, number, ret; + + for (i = 0; i < MAX_CLUSTERS; i++) + if (cpumask_test_cpu(cpu, &data->cluster[i].cpus)) + break; + + if (i == MAX_CLUSTERS) + return NOTIFY_OK; + + cluster = &data->cluster[i]; + + switch (action & ~CPU_TASKS_FROZEN) { + case CPU_ONLINE: + if (cluster->set_opp) { + dev = get_cpu_device(cpu); + if (!dev) + break; + ret = rockchip_cpufreq_set_opp_info(dev, cluster); + if (ret) + dev_err(dev, "Failed to set_opp_info\n"); + cluster->set_opp = false; + } + break; + + case CPU_POST_DEAD: + cpumask_and(&cpus, &cluster->cpus, cpu_online_mask); + number = cpumask_weight(&cpus); + if (!number) + cluster->set_opp = true; + break; + } + + return NOTIFY_OK; +} static int __init rockchip_cpufreq_driver_init(void) { + struct rockchip_cpufreq_data *data; struct platform_device *pdev; + struct cluster_info *cluster; + struct device *dev; + int i, cpu, ret; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->cluster = kzalloc(sizeof(*data->cluster) * MAX_CLUSTERS, + GFP_KERNEL); + if (!data->cluster) { + kfree(data); + return -ENOMEM; + } + + for_each_online_cpu(cpu) { + for (i = 0; i < MAX_CLUSTERS; i++) { + cluster = &data->cluster[i]; + + if (cpumask_test_cpu(cpu, &cluster->cpus)) + break; + + if (!cpumask_empty(&cluster->cpus)) + continue; + + dev = get_cpu_device(cpu); + if (!dev) + return -ENODEV; + + ret = dev_pm_opp_of_get_sharing_cpus(dev, + &cluster->cpus); + if (ret) { + /* Don't supportoperating-points-v2, break */ + if (ret == -ENOENT) + break; + return ret; + } + break; + } + } + + for (i = 0; i < MAX_CLUSTERS; i++) { + cluster = &data->cluster[i]; + + if (cpumask_empty(&cluster->cpus)) + continue; + + cpu = cpumask_first_and(&cluster->cpus, cpu_online_mask); + dev = get_cpu_device(cpu); + if (!dev) + return -ENODEV; + + ret = rockchip_cpufreq_of_parse_dt(dev, cluster); + if (!ret) { + ret = rockchip_cpufreq_set_opp_info(dev, cluster); + if (ret) + dev_err(dev, "Failed to set_opp_info\n"); + } else { + dev_err(dev, "Failed to parse_dt\n"); + } + } + + data->hotcpu_notify.notifier_call = rockchip_hotcpu_notifier; + register_hotcpu_notifier(&data->hotcpu_notify); pdev = platform_device_register_simple("cpufreq-dt", -1, NULL, 0); + return PTR_ERR_OR_ZERO(pdev); } module_init(rockchip_cpufreq_driver_init); -MODULE_AUTHOR("Xiao Feng "); +MODULE_AUTHOR("Finley Xiao "); MODULE_DESCRIPTION("Rockchip cpufreq driver"); MODULE_LICENSE("GPL v2"); -- 2.34.1