/* * Rockchip CPUFreq Driver * * 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 * published by the Free Software Foundation. * * This program is distributed "as is" WITHOUT ANY WARRANTY of any * kind, whether express or implied; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq-dt.h" #include "rockchip-cpufreq.h" #include "../clk/rockchip/clk.h" #define LEAKAGE_INVALID 0xff struct cluster_info { struct opp_table *opp_table; struct list_head list_head; cpumask_t cpus; unsigned int reboot_freq; unsigned int threshold_freq; unsigned int scale_rate; unsigned int temp_limit_rate; int volt_sel; int scale; int process; bool offline; bool freq_limit; bool is_check_init; }; static LIST_HEAD(cluster_info_list); static __maybe_unused int px30_get_soc_info(struct device *dev, struct device_node *np, int *bin, int *process) { int ret = 0; u8 value = 0; if (!bin) return 0; if (of_property_match_string(np, "nvmem-cell-names", "performance") >= 0) { ret = rockchip_nvmem_cell_read_u8(np, "performance", &value); if (ret) { dev_err(dev, "Failed to get soc performance value\n"); return ret; } *bin = value; } if (*bin >= 0) dev_info(dev, "bin=%d\n", *bin); return ret; } static __maybe_unused int rk3288_get_soc_info(struct device *dev, struct device_node *np, int *bin, int *process) { int ret = 0; u8 value = 0; char *name; if (!bin) goto next; if (of_property_match_string(np, "nvmem-cell-names", "special") >= 0) { ret = rockchip_nvmem_cell_read_u8(np, "special", &value); if (ret) { dev_err(dev, "Failed to get soc special value\n"); goto out; } if (value == 0xc) *bin = 0; else *bin = 1; } if (soc_is_rk3288w()) name = "performance-w"; else name = "performance"; if (of_property_match_string(np, "nvmem-cell-names", name) >= 0) { ret = rockchip_nvmem_cell_read_u8(np, name, &value); if (ret) { dev_err(dev, "Failed to get soc performance value\n"); goto out; } if (value & 0x2) *bin = 3; else if (value & 0x01) *bin = 2; } if (*bin >= 0) dev_info(dev, "bin=%d\n", *bin); next: if (!process) goto out; if (of_property_match_string(np, "nvmem-cell-names", "process") >= 0) { ret = rockchip_nvmem_cell_read_u8(np, "process", &value); if (ret) { dev_err(dev, "Failed to get soc process version\n"); goto out; } if (soc_is_rk3288() && (value == 0 || value == 1)) *process = 0; } if (*process >= 0) dev_info(dev, "process=%d\n", *process); out: return ret; } static __maybe_unused int rk3399_get_soc_info(struct device *dev, struct device_node *np, int *bin, int *process) { int ret = 0; u8 value = 0; if (!bin) return 0; if (of_property_match_string(np, "nvmem-cell-names", "specification_serial_number") >= 0) { ret = rockchip_nvmem_cell_read_u8(np, "specification_serial_number", &value); if (ret) { dev_err(dev, "Failed to get specification_serial_number\n"); goto out; } if (value == 0xb) { *bin = 0; } else if (value == 0x1) { if (of_property_match_string(np, "nvmem-cell-names", "customer_demand") >= 0) { ret = rockchip_nvmem_cell_read_u8(np, "customer_demand", &value); if (ret) { dev_err(dev, "Failed to get customer_demand\n"); goto out; } if (value == 0x0) *bin = 0; else *bin = 1; } } else if (value == 0x10) { *bin = 1; } } out: if (*bin >= 0) dev_info(dev, "bin=%d\n", *bin); return ret; } static __maybe_unused int rv1126_get_soc_info(struct device *dev, struct device_node *np, int *bin, int *process) { int ret = 0; u8 value = 0; if (of_property_match_string(np, "nvmem-cell-names", "performance") >= 0) { ret = rockchip_nvmem_cell_read_u8(np, "performance", &value); if (ret) { dev_err(dev, "Failed to get soc performance value\n"); return ret; } if (value == 0x1) *bin = 1; else *bin = 0; } if (*bin >= 0) dev_info(dev, "bin=%d\n", *bin); return ret; } static const struct of_device_id rockchip_cpufreq_of_match[] = { #ifdef CONFIG_CPU_PX30 { .compatible = "rockchip,px30", .data = (void *)&px30_get_soc_info, }, #endif #ifdef CONFIG_CPU_RK3288 { .compatible = "rockchip,rk3288", .data = (void *)&rk3288_get_soc_info, }, { .compatible = "rockchip,rk3288w", .data = (void *)&rk3288_get_soc_info, }, #endif #ifdef CONFIG_CPU_PX30 { .compatible = "rockchip,rk3326", .data = (void *)&px30_get_soc_info, }, #endif #ifdef CONFIG_CPU_RK3399 { .compatible = "rockchip,rk3399", .data = (void *)&rk3399_get_soc_info, }, #endif #ifdef CONFIG_CPU_RV1126 { .compatible = "rockchip,rv1109", .data = (void *)&rv1126_get_soc_info, }, { .compatible = "rockchip,rv1126", .data = (void *)&rv1126_get_soc_info, }, #endif {}, }; static struct cluster_info *rockchip_cluster_info_lookup(int cpu) { struct cluster_info *cluster; list_for_each_entry(cluster, &cluster_info_list, list_head) { if (cpumask_test_cpu(cpu, &cluster->cpus)) return cluster; } return NULL; } static struct cluster_info *rockchip_cluster_lookup_by_dev(struct device *dev) { struct cluster_info *cluster; struct device *cpu_dev; int cpu; list_for_each_entry(cluster, &cluster_info_list, list_head) { for_each_cpu(cpu, &cluster->cpus) { cpu_dev = get_cpu_device(cpu); if (!cpu_dev) continue; if (cpu_dev == dev) return cluster; } } return NULL; } static int rockchip_cpufreq_cluster_init(int cpu, struct cluster_info *cluster) { struct device_node *np; struct property *pp; struct device *dev; char *reg_name = NULL; int ret = 0, bin = -EINVAL; cluster->process = -EINVAL; cluster->volt_sel = -EINVAL; cluster->scale = 0; dev = get_cpu_device(cpu); if (!dev) return -ENODEV; pp = of_find_property(dev->of_node, "cpu-supply", NULL); if (pp) { reg_name = "cpu"; } else { pp = of_find_property(dev->of_node, "cpu0-supply", NULL); if (pp) reg_name = "cpu0"; else return -ENOENT; } np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); if (!np) { dev_warn(dev, "OPP-v2 not supported\n"); return -ENOENT; } ret = dev_pm_opp_of_get_sharing_cpus(dev, &cluster->cpus); if (ret) { dev_err(dev, "Failed to get sharing cpus\n"); goto np_err; } of_property_read_u32(np, "rockchip,threshold-freq", &cluster->threshold_freq); cluster->freq_limit = of_property_read_bool(np, "rockchip,freq-limit"); rockchip_get_soc_info(dev, rockchip_cpufreq_of_match, &bin, &cluster->process); rockchip_get_scale_volt_sel(dev, "cpu_leakage", reg_name, bin, cluster->process, &cluster->scale, &cluster->volt_sel); np_err: of_node_put(np); return ret; } int rockchip_cpufreq_check_rate_volt(struct device *dev) { struct cluster_info *cluster; cluster = rockchip_cluster_lookup_by_dev(dev); if (!cluster) return -EINVAL; if (cluster->is_check_init) return 0; dev_pm_opp_check_rate_volt(dev, true); cluster->is_check_init = true; return 0; } EXPORT_SYMBOL_GPL(rockchip_cpufreq_check_rate_volt); int rockchip_cpufreq_set_opp_info(struct device *dev) { struct cluster_info *cluster; cluster = rockchip_cluster_lookup_by_dev(dev); if (!cluster) return -EINVAL; cluster->opp_table = rockchip_set_opp_prop_name(dev, cluster->process, cluster->volt_sel); if (IS_ERR(cluster->opp_table)) { dev_err(dev, "Failed to set prop name\n"); return PTR_ERR(cluster->opp_table); } return 0; } EXPORT_SYMBOL_GPL(rockchip_cpufreq_set_opp_info); void rockchip_cpufreq_put_opp_info(struct device *dev) { struct cluster_info *cluster; cluster = rockchip_cluster_lookup_by_dev(dev); if (!cluster) return; if (!IS_ERR_OR_NULL(cluster->opp_table)) dev_pm_opp_put_prop_name(cluster->opp_table); } EXPORT_SYMBOL_GPL(rockchip_cpufreq_put_opp_info); int rockchip_cpufreq_adjust_power_scale(struct device *dev) { struct cluster_info *cluster; cluster = rockchip_cluster_lookup_by_dev(dev); if (!cluster) return -EINVAL; rockchip_adjust_power_scale(dev, cluster->scale); return 0; } EXPORT_SYMBOL_GPL(rockchip_cpufreq_adjust_power_scale); int rockchip_cpufreq_suspend(struct cpufreq_policy *policy) { int ret = 0; ret = cpufreq_generic_suspend(policy); if (!ret) rockchip_monitor_suspend_low_temp_adjust(policy->cpu); return ret; } EXPORT_SYMBOL_GPL(rockchip_cpufreq_suspend); static struct cpufreq_policy *rockchip_get_policy(struct cluster_info *cluster) { int first_cpu; first_cpu = cpumask_first_and(&cluster->cpus, cpu_online_mask); if (first_cpu >= nr_cpu_ids) return NULL; return cpufreq_cpu_get(first_cpu); } /** * rockchip_cpufreq_adjust_target() - Adjust cpu target frequency * @cpu: CPU number * @freq: Expected target frequency * * This adjusts cpu target frequency for reducing power consumption. * Only one cluster can eanble frequency limit, and the cluster's * maximum frequency will be limited to its threshold frequency, if the * other cluster's frequency is geater than or equal to its threshold * frequency. */ unsigned int rockchip_cpufreq_adjust_target(int cpu, unsigned int freq) { struct cpufreq_policy *policy; struct cluster_info *cluster, *temp; cluster = rockchip_cluster_info_lookup(cpu); if (!cluster || !cluster->threshold_freq) goto adjust_out; if (cluster->freq_limit) { if (freq <= cluster->threshold_freq) goto adjust_out; list_for_each_entry(temp, &cluster_info_list, list_head) { if (temp->freq_limit || temp == cluster || temp->offline) continue; policy = rockchip_get_policy(temp); if (!policy) continue; if (temp->threshold_freq && temp->threshold_freq <= policy->cur) { cpufreq_cpu_put(policy); return cluster->threshold_freq; } cpufreq_cpu_put(policy); } } else { if (freq < cluster->threshold_freq) goto adjust_out; list_for_each_entry(temp, &cluster_info_list, list_head) { if (!temp->freq_limit || temp == cluster || temp->offline) continue; policy = rockchip_get_policy(temp); if (!policy) continue; if (temp->threshold_freq && temp->threshold_freq < policy->cur) cpufreq_driver_target(policy, temp->threshold_freq, CPUFREQ_RELATION_H); cpufreq_cpu_put(policy); } } adjust_out: return freq; } EXPORT_SYMBOL_GPL(rockchip_cpufreq_adjust_target); static int __init rockchip_cpufreq_driver_init(void) { struct cluster_info *cluster, *pos; struct cpufreq_dt_platform_data pdata = {0}; int cpu, ret, i = 0; for_each_possible_cpu(cpu) { cluster = rockchip_cluster_info_lookup(cpu); if (cluster) continue; cluster = kzalloc(sizeof(*cluster), GFP_KERNEL); if (!cluster) { ret = -ENOMEM; goto release_cluster_info; } ret = rockchip_cpufreq_cluster_init(cpu, cluster); if (ret) { if (ret != -ENOENT) { pr_err("Failed to initialize dvfs info cpu%d\n", cpu); goto release_cluster_info; } /* * As the OPP document said, only one OPP binding * should be used per device. * And if there are multiple clusters on rockchip * platforms, we should use operating-points-v2. * So if don't support operating-points-v2, there must * be only one cluster, the list shuold be null. */ list_for_each_entry(pos, &cluster_info_list, list_head) i++; if (i) goto release_cluster_info; list_add(&cluster->list_head, &cluster_info_list); goto next; } list_add(&cluster->list_head, &cluster_info_list); } next: pdata.have_governor_per_policy = true; pdata.suspend = rockchip_cpufreq_suspend; return PTR_ERR_OR_ZERO(platform_device_register_data(NULL, "cpufreq-dt", -1, (void *)&pdata, sizeof(struct cpufreq_dt_platform_data))); release_cluster_info: list_for_each_entry_safe(cluster, pos, &cluster_info_list, list_head) { list_del(&cluster->list_head); kfree(cluster); } return ret; } module_init(rockchip_cpufreq_driver_init); MODULE_AUTHOR("Finley Xiao "); MODULE_DESCRIPTION("Rockchip cpufreq driver"); MODULE_LICENSE("GPL v2");