13d0407baSopenharmony_ci/* 23d0407baSopenharmony_ci * Rockchip CPUFreq Driver 33d0407baSopenharmony_ci * 43d0407baSopenharmony_ci * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd 53d0407baSopenharmony_ci * 63d0407baSopenharmony_ci * This program is free software; you can redistribute it and/or modify 73d0407baSopenharmony_ci * it under the terms of the GNU General Public License version 2 as 83d0407baSopenharmony_ci * published by the Free Software Foundation. 93d0407baSopenharmony_ci * 103d0407baSopenharmony_ci * This program is distributed "as is" WITHOUT ANY WARRANTY of any 113d0407baSopenharmony_ci * kind, whether express or implied; without even the implied warranty 123d0407baSopenharmony_ci * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 133d0407baSopenharmony_ci * GNU General Public License for more details. 143d0407baSopenharmony_ci */ 153d0407baSopenharmony_ci 163d0407baSopenharmony_ci#include <linux/clk.h> 173d0407baSopenharmony_ci#include <linux/cpu.h> 183d0407baSopenharmony_ci#include <linux/cpufreq.h> 193d0407baSopenharmony_ci#include <linux/err.h> 203d0407baSopenharmony_ci#include <linux/init.h> 213d0407baSopenharmony_ci#include <linux/kernel.h> 223d0407baSopenharmony_ci#include <linux/mfd/syscon.h> 233d0407baSopenharmony_ci#include <linux/module.h> 243d0407baSopenharmony_ci#include <linux/nvmem-consumer.h> 253d0407baSopenharmony_ci#include <linux/of.h> 263d0407baSopenharmony_ci#include <linux/of_address.h> 273d0407baSopenharmony_ci#include <linux/platform_device.h> 283d0407baSopenharmony_ci#include <linux/pm_opp.h> 293d0407baSopenharmony_ci#include <linux/slab.h> 303d0407baSopenharmony_ci#include <linux/regmap.h> 313d0407baSopenharmony_ci#include <linux/regulator/consumer.h> 323d0407baSopenharmony_ci#include <linux/rockchip/cpu.h> 333d0407baSopenharmony_ci#include <soc/rockchip/rockchip_opp_select.h> 343d0407baSopenharmony_ci#include <soc/rockchip/rockchip_system_monitor.h> 353d0407baSopenharmony_ci 363d0407baSopenharmony_ci#include "cpufreq-dt.h" 373d0407baSopenharmony_ci#include "rockchip-cpufreq.h" 383d0407baSopenharmony_ci 393d0407baSopenharmony_ci#define CPUFREQ_INTERNAL_VERSION 0x80 403d0407baSopenharmony_ci#define CPUFREQ_LENGTH_MARGIN 0x1 413d0407baSopenharmony_ci#define CPUFREQ_INTERMEDIATE_RATE (CPUFREQ_INTERNAL_VERSION | \ 423d0407baSopenharmony_ci CPUFREQ_LENGTH_MARGIN) 433d0407baSopenharmony_ci 443d0407baSopenharmony_cistruct cluster_info { 453d0407baSopenharmony_ci struct list_head list_head; 463d0407baSopenharmony_ci struct monitor_dev_info *mdev_info; 473d0407baSopenharmony_ci struct rockchip_opp_info opp_info; 483d0407baSopenharmony_ci cpumask_t cpus; 493d0407baSopenharmony_ci int scale; 503d0407baSopenharmony_ci}; 513d0407baSopenharmony_cistatic LIST_HEAD(cluster_info_list); 523d0407baSopenharmony_ci 533d0407baSopenharmony_cistatic int px30_get_soc_info(struct device *dev, struct device_node *np, 543d0407baSopenharmony_ci int *bin, int *process) 553d0407baSopenharmony_ci{ 563d0407baSopenharmony_ci int ret = 0; 573d0407baSopenharmony_ci u8 value = 0; 583d0407baSopenharmony_ci 593d0407baSopenharmony_ci if (!bin) 603d0407baSopenharmony_ci return 0; 613d0407baSopenharmony_ci 623d0407baSopenharmony_ci if (of_property_match_string(np, "nvmem-cell-names", 633d0407baSopenharmony_ci "performance") >= 0) { 643d0407baSopenharmony_ci ret = rockchip_nvmem_cell_read_u8(np, "performance", &value); 653d0407baSopenharmony_ci if (ret) { 663d0407baSopenharmony_ci dev_err(dev, "Failed to get soc performance value\n"); 673d0407baSopenharmony_ci return ret; 683d0407baSopenharmony_ci } 693d0407baSopenharmony_ci *bin = value; 703d0407baSopenharmony_ci } 713d0407baSopenharmony_ci if (*bin >= 0) 723d0407baSopenharmony_ci dev_info(dev, "bin=%d\n", *bin); 733d0407baSopenharmony_ci 743d0407baSopenharmony_ci return ret; 753d0407baSopenharmony_ci} 763d0407baSopenharmony_ci 773d0407baSopenharmony_cistatic int rk3288_get_soc_info(struct device *dev, struct device_node *np, 783d0407baSopenharmony_ci int *bin, int *process) 793d0407baSopenharmony_ci{ 803d0407baSopenharmony_ci int ret = 0; 813d0407baSopenharmony_ci u8 value = 0; 823d0407baSopenharmony_ci char *name; 833d0407baSopenharmony_ci 843d0407baSopenharmony_ci if (!bin) 853d0407baSopenharmony_ci goto next; 863d0407baSopenharmony_ci if (of_property_match_string(np, "nvmem-cell-names", "special") >= 0) { 873d0407baSopenharmony_ci ret = rockchip_nvmem_cell_read_u8(np, "special", &value); 883d0407baSopenharmony_ci if (ret) { 893d0407baSopenharmony_ci dev_err(dev, "Failed to get soc special value\n"); 903d0407baSopenharmony_ci goto out; 913d0407baSopenharmony_ci } 923d0407baSopenharmony_ci if (value == 0xc) 933d0407baSopenharmony_ci *bin = 0; 943d0407baSopenharmony_ci else 953d0407baSopenharmony_ci *bin = 1; 963d0407baSopenharmony_ci } 973d0407baSopenharmony_ci 983d0407baSopenharmony_ci if (soc_is_rk3288w()) 993d0407baSopenharmony_ci name = "performance-w"; 1003d0407baSopenharmony_ci else 1013d0407baSopenharmony_ci name = "performance"; 1023d0407baSopenharmony_ci 1033d0407baSopenharmony_ci if (of_property_match_string(np, "nvmem-cell-names", name) >= 0) { 1043d0407baSopenharmony_ci ret = rockchip_nvmem_cell_read_u8(np, name, &value); 1053d0407baSopenharmony_ci if (ret) { 1063d0407baSopenharmony_ci dev_err(dev, "Failed to get soc performance value\n"); 1073d0407baSopenharmony_ci goto out; 1083d0407baSopenharmony_ci } 1093d0407baSopenharmony_ci if (value & 0x2) 1103d0407baSopenharmony_ci *bin = 3; 1113d0407baSopenharmony_ci else if (value & 0x01) 1123d0407baSopenharmony_ci *bin = 2; 1133d0407baSopenharmony_ci } 1143d0407baSopenharmony_ci if (*bin >= 0) 1153d0407baSopenharmony_ci dev_info(dev, "bin=%d\n", *bin); 1163d0407baSopenharmony_ci 1173d0407baSopenharmony_cinext: 1183d0407baSopenharmony_ci if (!process) 1193d0407baSopenharmony_ci goto out; 1203d0407baSopenharmony_ci if (of_property_match_string(np, "nvmem-cell-names", 1213d0407baSopenharmony_ci "process") >= 0) { 1223d0407baSopenharmony_ci ret = rockchip_nvmem_cell_read_u8(np, "process", &value); 1233d0407baSopenharmony_ci if (ret) { 1243d0407baSopenharmony_ci dev_err(dev, "Failed to get soc process version\n"); 1253d0407baSopenharmony_ci goto out; 1263d0407baSopenharmony_ci } 1273d0407baSopenharmony_ci if (soc_is_rk3288() && (value == 0 || value == 1)) 1283d0407baSopenharmony_ci *process = 0; 1293d0407baSopenharmony_ci } 1303d0407baSopenharmony_ci if (*process >= 0) 1313d0407baSopenharmony_ci dev_info(dev, "process=%d\n", *process); 1323d0407baSopenharmony_ci 1333d0407baSopenharmony_ciout: 1343d0407baSopenharmony_ci return ret; 1353d0407baSopenharmony_ci} 1363d0407baSopenharmony_ci 1373d0407baSopenharmony_cistatic int rk3399_get_soc_info(struct device *dev, struct device_node *np, 1383d0407baSopenharmony_ci int *bin, int *process) 1393d0407baSopenharmony_ci{ 1403d0407baSopenharmony_ci int ret = 0; 1413d0407baSopenharmony_ci u8 value = 0; 1423d0407baSopenharmony_ci 1433d0407baSopenharmony_ci if (!bin) 1443d0407baSopenharmony_ci return 0; 1453d0407baSopenharmony_ci 1463d0407baSopenharmony_ci if (of_property_match_string(np, "nvmem-cell-names", 1473d0407baSopenharmony_ci "specification_serial_number") >= 0) { 1483d0407baSopenharmony_ci ret = rockchip_nvmem_cell_read_u8(np, 1493d0407baSopenharmony_ci "specification_serial_number", 1503d0407baSopenharmony_ci &value); 1513d0407baSopenharmony_ci if (ret) { 1523d0407baSopenharmony_ci dev_err(dev, 1533d0407baSopenharmony_ci "Failed to get specification_serial_number\n"); 1543d0407baSopenharmony_ci goto out; 1553d0407baSopenharmony_ci } 1563d0407baSopenharmony_ci 1573d0407baSopenharmony_ci if (value == 0xb) { 1583d0407baSopenharmony_ci *bin = 0; 1593d0407baSopenharmony_ci } else if (value == 0x1) { 1603d0407baSopenharmony_ci if (of_property_match_string(np, "nvmem-cell-names", 1613d0407baSopenharmony_ci "customer_demand") >= 0) { 1623d0407baSopenharmony_ci ret = rockchip_nvmem_cell_read_u8(np, 1633d0407baSopenharmony_ci "customer_demand", 1643d0407baSopenharmony_ci &value); 1653d0407baSopenharmony_ci if (ret) { 1663d0407baSopenharmony_ci dev_err(dev, "Failed to get customer_demand\n"); 1673d0407baSopenharmony_ci goto out; 1683d0407baSopenharmony_ci } 1693d0407baSopenharmony_ci if (value == 0x0) 1703d0407baSopenharmony_ci *bin = 0; 1713d0407baSopenharmony_ci else 1723d0407baSopenharmony_ci *bin = 1; 1733d0407baSopenharmony_ci } 1743d0407baSopenharmony_ci } else if (value == 0x10) { 1753d0407baSopenharmony_ci *bin = 1; 1763d0407baSopenharmony_ci } 1773d0407baSopenharmony_ci } 1783d0407baSopenharmony_ci 1793d0407baSopenharmony_ciout: 1803d0407baSopenharmony_ci if (*bin >= 0) 1813d0407baSopenharmony_ci dev_info(dev, "bin=%d\n", *bin); 1823d0407baSopenharmony_ci 1833d0407baSopenharmony_ci return ret; 1843d0407baSopenharmony_ci} 1853d0407baSopenharmony_ci 1863d0407baSopenharmony_cistatic int rk3588_cpu_set_read_margin(struct device *dev, 1873d0407baSopenharmony_ci struct rockchip_opp_info *opp_info, 1883d0407baSopenharmony_ci unsigned long volt) 1893d0407baSopenharmony_ci{ 1903d0407baSopenharmony_ci bool is_found = false; 1913d0407baSopenharmony_ci u32 rm; 1923d0407baSopenharmony_ci int i; 1933d0407baSopenharmony_ci 1943d0407baSopenharmony_ci if (!opp_info->volt_rm_tbl) 1953d0407baSopenharmony_ci return 0; 1963d0407baSopenharmony_ci 1973d0407baSopenharmony_ci for (i = 0; opp_info->volt_rm_tbl[i].rm != VOLT_RM_TABLE_END; i++) { 1983d0407baSopenharmony_ci if (volt >= opp_info->volt_rm_tbl[i].volt) { 1993d0407baSopenharmony_ci rm = opp_info->volt_rm_tbl[i].rm; 2003d0407baSopenharmony_ci is_found = true; 2013d0407baSopenharmony_ci break; 2023d0407baSopenharmony_ci } 2033d0407baSopenharmony_ci } 2043d0407baSopenharmony_ci 2053d0407baSopenharmony_ci if (!is_found) 2063d0407baSopenharmony_ci return 0; 2073d0407baSopenharmony_ci if (rm == opp_info->current_rm) 2083d0407baSopenharmony_ci return 0; 2093d0407baSopenharmony_ci 2103d0407baSopenharmony_ci dev_dbg(dev, "set rm to %d\n", rm); 2113d0407baSopenharmony_ci if (opp_info->grf) { 2123d0407baSopenharmony_ci regmap_write(opp_info->grf, 0x20, 0x001c0000 | (rm << 2)); 2133d0407baSopenharmony_ci regmap_write(opp_info->grf, 0x28, 0x003c0000 | (rm << 2)); 2143d0407baSopenharmony_ci regmap_write(opp_info->grf, 0x2c, 0x003c0000 | (rm << 2)); 2153d0407baSopenharmony_ci regmap_write(opp_info->grf, 0x30, 0x00200020); 2163d0407baSopenharmony_ci udelay(1); 2173d0407baSopenharmony_ci regmap_write(opp_info->grf, 0x30, 0x00200000); 2183d0407baSopenharmony_ci } 2193d0407baSopenharmony_ci if (opp_info->dsu_grf) { 2203d0407baSopenharmony_ci regmap_write(opp_info->dsu_grf, 0x20, 0x001c0000 | (rm << 2)); 2213d0407baSopenharmony_ci regmap_write(opp_info->dsu_grf, 0x28, 0x003c0000 | (rm << 2)); 2223d0407baSopenharmony_ci regmap_write(opp_info->dsu_grf, 0x2c, 0x003c0000 | (rm << 2)); 2233d0407baSopenharmony_ci regmap_write(opp_info->dsu_grf, 0x30, 0x001c0000 | (rm << 2)); 2243d0407baSopenharmony_ci regmap_write(opp_info->dsu_grf, 0x38, 0x001c0000 | (rm << 2)); 2253d0407baSopenharmony_ci regmap_write(opp_info->dsu_grf, 0x18, 0x40004000); 2263d0407baSopenharmony_ci udelay(1); 2273d0407baSopenharmony_ci regmap_write(opp_info->dsu_grf, 0x18, 0x40000000); 2283d0407baSopenharmony_ci } 2293d0407baSopenharmony_ci 2303d0407baSopenharmony_ci opp_info->current_rm = rm; 2313d0407baSopenharmony_ci 2323d0407baSopenharmony_ci return 0; 2333d0407baSopenharmony_ci} 2343d0407baSopenharmony_ci 2353d0407baSopenharmony_cistatic int rv1126_get_soc_info(struct device *dev, struct device_node *np, 2363d0407baSopenharmony_ci int *bin, int *process) 2373d0407baSopenharmony_ci{ 2383d0407baSopenharmony_ci int ret = 0; 2393d0407baSopenharmony_ci u8 value = 0; 2403d0407baSopenharmony_ci 2413d0407baSopenharmony_ci if (of_property_match_string(np, "nvmem-cell-names", "performance") >= 0) { 2423d0407baSopenharmony_ci ret = rockchip_nvmem_cell_read_u8(np, "performance", &value); 2433d0407baSopenharmony_ci if (ret) { 2443d0407baSopenharmony_ci dev_err(dev, "Failed to get soc performance value\n"); 2453d0407baSopenharmony_ci return ret; 2463d0407baSopenharmony_ci } 2473d0407baSopenharmony_ci if (value == 0x1) 2483d0407baSopenharmony_ci *bin = 1; 2493d0407baSopenharmony_ci else 2503d0407baSopenharmony_ci *bin = 0; 2513d0407baSopenharmony_ci } 2523d0407baSopenharmony_ci if (*bin >= 0) 2533d0407baSopenharmony_ci dev_info(dev, "bin=%d\n", *bin); 2543d0407baSopenharmony_ci 2553d0407baSopenharmony_ci return ret; 2563d0407baSopenharmony_ci} 2573d0407baSopenharmony_ci 2583d0407baSopenharmony_cistatic const struct rockchip_opp_data px30_cpu_opp_data = { 2593d0407baSopenharmony_ci .get_soc_info = px30_get_soc_info, 2603d0407baSopenharmony_ci}; 2613d0407baSopenharmony_ci 2623d0407baSopenharmony_cistatic const struct rockchip_opp_data rk3288_cpu_opp_data = { 2633d0407baSopenharmony_ci .get_soc_info = rk3288_get_soc_info, 2643d0407baSopenharmony_ci}; 2653d0407baSopenharmony_ci 2663d0407baSopenharmony_cistatic const struct rockchip_opp_data rk3399_cpu_opp_data = { 2673d0407baSopenharmony_ci .get_soc_info = rk3399_get_soc_info, 2683d0407baSopenharmony_ci}; 2693d0407baSopenharmony_ci 2703d0407baSopenharmony_cistatic const struct rockchip_opp_data rk3588_cpu_opp_data = { 2713d0407baSopenharmony_ci .set_read_margin = rk3588_cpu_set_read_margin, 2723d0407baSopenharmony_ci}; 2733d0407baSopenharmony_ci 2743d0407baSopenharmony_cistatic const struct rockchip_opp_data rv1126_cpu_opp_data = { 2753d0407baSopenharmony_ci .get_soc_info = rv1126_get_soc_info, 2763d0407baSopenharmony_ci}; 2773d0407baSopenharmony_ci 2783d0407baSopenharmony_cistatic const struct of_device_id rockchip_cpufreq_of_match[] = { 2793d0407baSopenharmony_ci { 2803d0407baSopenharmony_ci .compatible = "rockchip,px30", 2813d0407baSopenharmony_ci .data = (void *)&px30_cpu_opp_data, 2823d0407baSopenharmony_ci }, 2833d0407baSopenharmony_ci { 2843d0407baSopenharmony_ci .compatible = "rockchip,rk3288", 2853d0407baSopenharmony_ci .data = (void *)&rk3288_cpu_opp_data, 2863d0407baSopenharmony_ci }, 2873d0407baSopenharmony_ci { 2883d0407baSopenharmony_ci .compatible = "rockchip,rk3288w", 2893d0407baSopenharmony_ci .data = (void *)&rk3288_cpu_opp_data, 2903d0407baSopenharmony_ci }, 2913d0407baSopenharmony_ci { 2923d0407baSopenharmony_ci .compatible = "rockchip,rk3326", 2933d0407baSopenharmony_ci .data = (void *)&px30_cpu_opp_data, 2943d0407baSopenharmony_ci }, 2953d0407baSopenharmony_ci { 2963d0407baSopenharmony_ci .compatible = "rockchip,rk3399", 2973d0407baSopenharmony_ci .data = (void *)&rk3399_cpu_opp_data, 2983d0407baSopenharmony_ci }, 2993d0407baSopenharmony_ci { 3003d0407baSopenharmony_ci .compatible = "rockchip,rk3588", 3013d0407baSopenharmony_ci .data = (void *)&rk3588_cpu_opp_data, 3023d0407baSopenharmony_ci }, 3033d0407baSopenharmony_ci { 3043d0407baSopenharmony_ci .compatible = "rockchip,rv1109", 3053d0407baSopenharmony_ci .data = (void *)&rv1126_cpu_opp_data, 3063d0407baSopenharmony_ci }, 3073d0407baSopenharmony_ci { 3083d0407baSopenharmony_ci .compatible = "rockchip,rv1126", 3093d0407baSopenharmony_ci .data = (void *)&rv1126_cpu_opp_data, 3103d0407baSopenharmony_ci }, 3113d0407baSopenharmony_ci {}, 3123d0407baSopenharmony_ci}; 3133d0407baSopenharmony_ci 3143d0407baSopenharmony_cistatic struct cluster_info *rockchip_cluster_info_lookup(int cpu) 3153d0407baSopenharmony_ci{ 3163d0407baSopenharmony_ci struct cluster_info *cluster; 3173d0407baSopenharmony_ci 3183d0407baSopenharmony_ci list_for_each_entry(cluster, &cluster_info_list, list_head) { 3193d0407baSopenharmony_ci if (cpumask_test_cpu(cpu, &cluster->cpus)) 3203d0407baSopenharmony_ci return cluster; 3213d0407baSopenharmony_ci } 3223d0407baSopenharmony_ci 3233d0407baSopenharmony_ci return NULL; 3243d0407baSopenharmony_ci} 3253d0407baSopenharmony_ci 3263d0407baSopenharmony_cistatic int rockchip_cpufreq_set_volt(struct device *dev, 3273d0407baSopenharmony_ci struct regulator *reg, 3283d0407baSopenharmony_ci struct dev_pm_opp_supply *supply, 3293d0407baSopenharmony_ci char *reg_name) 3303d0407baSopenharmony_ci{ 3313d0407baSopenharmony_ci int ret; 3323d0407baSopenharmony_ci 3333d0407baSopenharmony_ci dev_dbg(dev, "%s: %s voltages (uV): %lu %lu %lu\n", __func__, reg_name, 3343d0407baSopenharmony_ci supply->u_volt_min, supply->u_volt, supply->u_volt_max); 3353d0407baSopenharmony_ci 3363d0407baSopenharmony_ci ret = regulator_set_voltage_triplet(reg, supply->u_volt_min, 3373d0407baSopenharmony_ci supply->u_volt, supply->u_volt_max); 3383d0407baSopenharmony_ci if (ret) 3393d0407baSopenharmony_ci dev_err(dev, "%s: failed to set voltage (%lu %lu %lu uV): %d\n", 3403d0407baSopenharmony_ci __func__, supply->u_volt_min, supply->u_volt, 3413d0407baSopenharmony_ci supply->u_volt_max, ret); 3423d0407baSopenharmony_ci 3433d0407baSopenharmony_ci return ret; 3443d0407baSopenharmony_ci} 3453d0407baSopenharmony_ci 3463d0407baSopenharmony_cistatic int rockchip_cpufreq_set_read_margin(struct device *dev, 3473d0407baSopenharmony_ci struct rockchip_opp_info *opp_info, 3483d0407baSopenharmony_ci unsigned long volt) 3493d0407baSopenharmony_ci{ 3503d0407baSopenharmony_ci if (opp_info->data && opp_info->data->set_read_margin) { 3513d0407baSopenharmony_ci opp_info->data->set_read_margin(dev, opp_info, volt); 3523d0407baSopenharmony_ci opp_info->volt_rm = volt; 3533d0407baSopenharmony_ci } 3543d0407baSopenharmony_ci 3553d0407baSopenharmony_ci return 0; 3563d0407baSopenharmony_ci} 3573d0407baSopenharmony_ci 3583d0407baSopenharmony_cistatic int 3593d0407baSopenharmony_cirockchip_cpufreq_set_intermediate_rate(struct rockchip_opp_info *opp_info, 3603d0407baSopenharmony_ci struct clk *clk, unsigned long new_freq) 3613d0407baSopenharmony_ci{ 3623d0407baSopenharmony_ci if (opp_info->data && opp_info->data->set_read_margin) 3633d0407baSopenharmony_ci return clk_set_rate(clk, new_freq | CPUFREQ_INTERMEDIATE_RATE); 3643d0407baSopenharmony_ci 3653d0407baSopenharmony_ci return 0; 3663d0407baSopenharmony_ci} 3673d0407baSopenharmony_ci 3683d0407baSopenharmony_cistatic int cpu_opp_helper(struct dev_pm_set_opp_data *data) 3693d0407baSopenharmony_ci{ 3703d0407baSopenharmony_ci struct dev_pm_opp_supply *old_supply_vdd = &data->old_opp.supplies[0]; 3713d0407baSopenharmony_ci struct dev_pm_opp_supply *old_supply_mem = &data->old_opp.supplies[1]; 3723d0407baSopenharmony_ci struct dev_pm_opp_supply *new_supply_vdd = &data->new_opp.supplies[0]; 3733d0407baSopenharmony_ci struct dev_pm_opp_supply *new_supply_mem = &data->new_opp.supplies[1]; 3743d0407baSopenharmony_ci struct regulator *vdd_reg = data->regulators[0]; 3753d0407baSopenharmony_ci struct regulator *mem_reg = data->regulators[1]; 3763d0407baSopenharmony_ci struct device *dev = data->dev; 3773d0407baSopenharmony_ci struct clk *clk = data->clk; 3783d0407baSopenharmony_ci struct cluster_info *cluster; 3793d0407baSopenharmony_ci struct rockchip_opp_info *opp_info; 3803d0407baSopenharmony_ci unsigned long old_freq = data->old_opp.rate; 3813d0407baSopenharmony_ci unsigned long new_freq = data->new_opp.rate; 3823d0407baSopenharmony_ci int ret = 0; 3833d0407baSopenharmony_ci 3843d0407baSopenharmony_ci cluster = rockchip_cluster_info_lookup(dev->id); 3853d0407baSopenharmony_ci if (!cluster) 3863d0407baSopenharmony_ci return -EINVAL; 3873d0407baSopenharmony_ci opp_info = &cluster->opp_info; 3883d0407baSopenharmony_ci 3893d0407baSopenharmony_ci /* Scaling up? Scale voltage before frequency */ 3903d0407baSopenharmony_ci if (new_freq >= old_freq) { 3913d0407baSopenharmony_ci ret = rockchip_cpufreq_set_intermediate_rate(opp_info, clk, 3923d0407baSopenharmony_ci new_freq); 3933d0407baSopenharmony_ci if (ret) { 3943d0407baSopenharmony_ci dev_err(dev, "%s: failed to set clk rate: %lu\n", 3953d0407baSopenharmony_ci __func__, new_freq); 3963d0407baSopenharmony_ci return -EINVAL; 3973d0407baSopenharmony_ci } 3983d0407baSopenharmony_ci ret = rockchip_cpufreq_set_volt(dev, mem_reg, new_supply_mem, 3993d0407baSopenharmony_ci "mem"); 4003d0407baSopenharmony_ci if (ret) 4013d0407baSopenharmony_ci goto restore_voltage; 4023d0407baSopenharmony_ci ret = rockchip_cpufreq_set_volt(dev, vdd_reg, new_supply_vdd, 4033d0407baSopenharmony_ci "vdd"); 4043d0407baSopenharmony_ci if (ret) 4053d0407baSopenharmony_ci goto restore_voltage; 4063d0407baSopenharmony_ci rockchip_cpufreq_set_read_margin(dev, opp_info, 4073d0407baSopenharmony_ci new_supply_vdd->u_volt); 4083d0407baSopenharmony_ci } 4093d0407baSopenharmony_ci 4103d0407baSopenharmony_ci /* Change frequency */ 4113d0407baSopenharmony_ci dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n", __func__, 4123d0407baSopenharmony_ci old_freq, new_freq); 4133d0407baSopenharmony_ci ret = clk_set_rate(clk, new_freq); 4143d0407baSopenharmony_ci if (ret) { 4153d0407baSopenharmony_ci dev_err(dev, "%s: failed to set clk rate: %d\n", __func__, ret); 4163d0407baSopenharmony_ci goto restore_rm; 4173d0407baSopenharmony_ci } 4183d0407baSopenharmony_ci 4193d0407baSopenharmony_ci /* Scaling down? Scale voltage after frequency */ 4203d0407baSopenharmony_ci if (new_freq < old_freq) { 4213d0407baSopenharmony_ci rockchip_cpufreq_set_read_margin(dev, opp_info, 4223d0407baSopenharmony_ci new_supply_vdd->u_volt); 4233d0407baSopenharmony_ci ret = rockchip_cpufreq_set_volt(dev, vdd_reg, new_supply_vdd, 4243d0407baSopenharmony_ci "vdd"); 4253d0407baSopenharmony_ci if (ret) 4263d0407baSopenharmony_ci goto restore_freq; 4273d0407baSopenharmony_ci ret = rockchip_cpufreq_set_volt(dev, mem_reg, new_supply_mem, 4283d0407baSopenharmony_ci "mem"); 4293d0407baSopenharmony_ci if (ret) 4303d0407baSopenharmony_ci goto restore_freq; 4313d0407baSopenharmony_ci } 4323d0407baSopenharmony_ci 4333d0407baSopenharmony_ci return 0; 4343d0407baSopenharmony_ci 4353d0407baSopenharmony_cirestore_freq: 4363d0407baSopenharmony_ci if (clk_set_rate(clk, old_freq)) 4373d0407baSopenharmony_ci dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n", 4383d0407baSopenharmony_ci __func__, old_freq); 4393d0407baSopenharmony_cirestore_rm: 4403d0407baSopenharmony_ci rockchip_cpufreq_set_read_margin(dev, opp_info, 4413d0407baSopenharmony_ci old_supply_vdd->u_volt); 4423d0407baSopenharmony_cirestore_voltage: 4433d0407baSopenharmony_ci rockchip_cpufreq_set_volt(dev, mem_reg, old_supply_mem, "mem"); 4443d0407baSopenharmony_ci rockchip_cpufreq_set_volt(dev, vdd_reg, old_supply_vdd, "vdd"); 4453d0407baSopenharmony_ci 4463d0407baSopenharmony_ci return ret; 4473d0407baSopenharmony_ci} 4483d0407baSopenharmony_ci 4493d0407baSopenharmony_cistatic int rockchip_cpufreq_cluster_init(int cpu, struct cluster_info *cluster) 4503d0407baSopenharmony_ci{ 4513d0407baSopenharmony_ci struct rockchip_opp_info *opp_info = &cluster->opp_info; 4523d0407baSopenharmony_ci struct opp_table *pname_table = NULL; 4533d0407baSopenharmony_ci struct opp_table *reg_table = NULL; 4543d0407baSopenharmony_ci struct opp_table *opp_table; 4553d0407baSopenharmony_ci struct device_node *np; 4563d0407baSopenharmony_ci struct device *dev; 4573d0407baSopenharmony_ci const char * const reg_names[] = {"cpu", "mem"}; 4583d0407baSopenharmony_ci char *reg_name = NULL; 4593d0407baSopenharmony_ci int bin = -EINVAL; 4603d0407baSopenharmony_ci int process = -EINVAL; 4613d0407baSopenharmony_ci int volt_sel = -EINVAL; 4623d0407baSopenharmony_ci int ret = 0; 4633d0407baSopenharmony_ci 4643d0407baSopenharmony_ci dev = get_cpu_device(cpu); 4653d0407baSopenharmony_ci if (!dev) 4663d0407baSopenharmony_ci return -ENODEV; 4673d0407baSopenharmony_ci 4683d0407baSopenharmony_ci if (of_find_property(dev->of_node, "cpu-supply", NULL)) 4693d0407baSopenharmony_ci reg_name = "cpu"; 4703d0407baSopenharmony_ci else if (of_find_property(dev->of_node, "cpu0-supply", NULL)) 4713d0407baSopenharmony_ci reg_name = "cpu0"; 4723d0407baSopenharmony_ci else 4733d0407baSopenharmony_ci return -ENOENT; 4743d0407baSopenharmony_ci 4753d0407baSopenharmony_ci np = of_parse_phandle(dev->of_node, "operating-points-v2", 0); 4763d0407baSopenharmony_ci if (!np) { 4773d0407baSopenharmony_ci dev_warn(dev, "OPP-v2 not supported\n"); 4783d0407baSopenharmony_ci return -ENOENT; 4793d0407baSopenharmony_ci } 4803d0407baSopenharmony_ci 4813d0407baSopenharmony_ci ret = dev_pm_opp_of_get_sharing_cpus(dev, &cluster->cpus); 4823d0407baSopenharmony_ci if (ret) { 4833d0407baSopenharmony_ci dev_err(dev, "Failed to get sharing cpus\n"); 4843d0407baSopenharmony_ci goto np_err; 4853d0407baSopenharmony_ci } 4863d0407baSopenharmony_ci 4873d0407baSopenharmony_ci rockchip_get_opp_data(rockchip_cpufreq_of_match, opp_info); 4883d0407baSopenharmony_ci if (opp_info->data && opp_info->data->set_read_margin) { 4893d0407baSopenharmony_ci opp_info->current_rm = UINT_MAX; 4903d0407baSopenharmony_ci opp_info->grf = syscon_regmap_lookup_by_phandle(np, 4913d0407baSopenharmony_ci "rockchip,grf"); 4923d0407baSopenharmony_ci if (IS_ERR(opp_info->grf)) 4933d0407baSopenharmony_ci opp_info->grf = NULL; 4943d0407baSopenharmony_ci opp_info->dsu_grf = 4953d0407baSopenharmony_ci syscon_regmap_lookup_by_phandle(np, "rockchip,dsu-grf"); 4963d0407baSopenharmony_ci if (IS_ERR(opp_info->dsu_grf)) 4973d0407baSopenharmony_ci opp_info->dsu_grf = NULL; 4983d0407baSopenharmony_ci rockchip_get_volt_rm_table(dev, np, "volt-mem-read-margin", 4993d0407baSopenharmony_ci &opp_info->volt_rm_tbl); 5003d0407baSopenharmony_ci } 5013d0407baSopenharmony_ci if (opp_info->data && opp_info->data->get_soc_info) 5023d0407baSopenharmony_ci opp_info->data->get_soc_info(dev, np, &bin, &process); 5033d0407baSopenharmony_ci rockchip_get_scale_volt_sel(dev, "cpu_leakage", reg_name, bin, process, 5043d0407baSopenharmony_ci &cluster->scale, &volt_sel); 5053d0407baSopenharmony_ci pname_table = rockchip_set_opp_prop_name(dev, process, volt_sel); 5063d0407baSopenharmony_ci if (IS_ERR(pname_table)) { 5073d0407baSopenharmony_ci ret = PTR_ERR(pname_table); 5083d0407baSopenharmony_ci goto np_err; 5093d0407baSopenharmony_ci } 5103d0407baSopenharmony_ci 5113d0407baSopenharmony_ci if (of_find_property(dev->of_node, "cpu-supply", NULL) && 5123d0407baSopenharmony_ci of_find_property(dev->of_node, "mem-supply", NULL)) { 5133d0407baSopenharmony_ci reg_table = dev_pm_opp_set_regulators(dev, reg_names, 5143d0407baSopenharmony_ci ARRAY_SIZE(reg_names)); 5153d0407baSopenharmony_ci if (IS_ERR(reg_table)) { 5163d0407baSopenharmony_ci ret = PTR_ERR(reg_table); 5173d0407baSopenharmony_ci goto pname_opp_table; 5183d0407baSopenharmony_ci } 5193d0407baSopenharmony_ci opp_table = dev_pm_opp_register_set_opp_helper(dev, 5203d0407baSopenharmony_ci cpu_opp_helper); 5213d0407baSopenharmony_ci if (IS_ERR(opp_table)) { 5223d0407baSopenharmony_ci ret = PTR_ERR(opp_table); 5233d0407baSopenharmony_ci goto reg_opp_table; 5243d0407baSopenharmony_ci } 5253d0407baSopenharmony_ci } 5263d0407baSopenharmony_ci 5273d0407baSopenharmony_ci of_node_put(np); 5283d0407baSopenharmony_ci 5293d0407baSopenharmony_ci return 0; 5303d0407baSopenharmony_ci 5313d0407baSopenharmony_cireg_opp_table: 5323d0407baSopenharmony_ci if (reg_table) 5333d0407baSopenharmony_ci dev_pm_opp_put_regulators(reg_table); 5343d0407baSopenharmony_cipname_opp_table: 5353d0407baSopenharmony_ci if (pname_table) 5363d0407baSopenharmony_ci dev_pm_opp_put_prop_name(pname_table); 5373d0407baSopenharmony_cinp_err: 5383d0407baSopenharmony_ci of_node_put(np); 5393d0407baSopenharmony_ci 5403d0407baSopenharmony_ci return ret; 5413d0407baSopenharmony_ci} 5423d0407baSopenharmony_ci 5433d0407baSopenharmony_ciint rockchip_cpufreq_adjust_power_scale(struct device *dev) 5443d0407baSopenharmony_ci{ 5453d0407baSopenharmony_ci struct cluster_info *cluster; 5463d0407baSopenharmony_ci 5473d0407baSopenharmony_ci cluster = rockchip_cluster_info_lookup(dev->id); 5483d0407baSopenharmony_ci if (!cluster) 5493d0407baSopenharmony_ci return -EINVAL; 5503d0407baSopenharmony_ci rockchip_adjust_power_scale(dev, cluster->scale); 5513d0407baSopenharmony_ci 5523d0407baSopenharmony_ci return 0; 5533d0407baSopenharmony_ci} 5543d0407baSopenharmony_ciEXPORT_SYMBOL_GPL(rockchip_cpufreq_adjust_power_scale); 5553d0407baSopenharmony_ci 5563d0407baSopenharmony_ciint rockchip_cpufreq_opp_set_rate(struct device *dev, unsigned long target_freq) 5573d0407baSopenharmony_ci{ 5583d0407baSopenharmony_ci struct cluster_info *cluster; 5593d0407baSopenharmony_ci int ret = 0; 5603d0407baSopenharmony_ci 5613d0407baSopenharmony_ci cluster = rockchip_cluster_info_lookup(dev->id); 5623d0407baSopenharmony_ci if (!cluster) 5633d0407baSopenharmony_ci return -EINVAL; 5643d0407baSopenharmony_ci 5653d0407baSopenharmony_ci rockchip_monitor_volt_adjust_lock(cluster->mdev_info); 5663d0407baSopenharmony_ci ret = dev_pm_opp_set_rate(dev, target_freq); 5673d0407baSopenharmony_ci rockchip_monitor_volt_adjust_unlock(cluster->mdev_info); 5683d0407baSopenharmony_ci 5693d0407baSopenharmony_ci return ret; 5703d0407baSopenharmony_ci} 5713d0407baSopenharmony_ciEXPORT_SYMBOL_GPL(rockchip_cpufreq_opp_set_rate); 5723d0407baSopenharmony_ci 5733d0407baSopenharmony_cistatic int rockchip_cpufreq_suspend(struct cpufreq_policy *policy) 5743d0407baSopenharmony_ci{ 5753d0407baSopenharmony_ci int ret = 0; 5763d0407baSopenharmony_ci 5773d0407baSopenharmony_ci ret = cpufreq_generic_suspend(policy); 5783d0407baSopenharmony_ci if (!ret) 5793d0407baSopenharmony_ci rockchip_monitor_suspend_low_temp_adjust(policy->cpu); 5803d0407baSopenharmony_ci 5813d0407baSopenharmony_ci return ret; 5823d0407baSopenharmony_ci} 5833d0407baSopenharmony_ci 5843d0407baSopenharmony_cistatic int rockchip_cpufreq_notifier(struct notifier_block *nb, 5853d0407baSopenharmony_ci unsigned long event, void *data) 5863d0407baSopenharmony_ci{ 5873d0407baSopenharmony_ci struct device *dev; 5883d0407baSopenharmony_ci struct cpufreq_policy *policy = data; 5893d0407baSopenharmony_ci struct cluster_info *cluster; 5903d0407baSopenharmony_ci struct monitor_dev_profile *mdevp = NULL; 5913d0407baSopenharmony_ci struct monitor_dev_info *mdev_info = NULL; 5923d0407baSopenharmony_ci 5933d0407baSopenharmony_ci dev = get_cpu_device(policy->cpu); 5943d0407baSopenharmony_ci if (!dev) 5953d0407baSopenharmony_ci return NOTIFY_BAD; 5963d0407baSopenharmony_ci 5973d0407baSopenharmony_ci cluster = rockchip_cluster_info_lookup(policy->cpu); 5983d0407baSopenharmony_ci if (!cluster) 5993d0407baSopenharmony_ci return NOTIFY_BAD; 6003d0407baSopenharmony_ci 6013d0407baSopenharmony_ci if (event == CPUFREQ_CREATE_POLICY) { 6023d0407baSopenharmony_ci mdevp = kzalloc(sizeof(*mdevp), GFP_KERNEL); 6033d0407baSopenharmony_ci if (!mdevp) 6043d0407baSopenharmony_ci return NOTIFY_BAD; 6053d0407baSopenharmony_ci mdevp->type = MONITOR_TPYE_CPU; 6063d0407baSopenharmony_ci mdevp->low_temp_adjust = rockchip_monitor_cpu_low_temp_adjust; 6073d0407baSopenharmony_ci mdevp->high_temp_adjust = rockchip_monitor_cpu_high_temp_adjust; 6083d0407baSopenharmony_ci mdevp->update_volt = rockchip_monitor_check_rate_volt; 6093d0407baSopenharmony_ci mdevp->data = (void *)policy; 6103d0407baSopenharmony_ci mdevp->opp_info = &cluster->opp_info; 6113d0407baSopenharmony_ci cpumask_copy(&mdevp->allowed_cpus, policy->cpus); 6123d0407baSopenharmony_ci mdev_info = rockchip_system_monitor_register(dev, mdevp); 6133d0407baSopenharmony_ci if (IS_ERR(mdev_info)) { 6143d0407baSopenharmony_ci kfree(mdevp); 6153d0407baSopenharmony_ci dev_err(dev, "failed to register system monitor\n"); 6163d0407baSopenharmony_ci return NOTIFY_BAD; 6173d0407baSopenharmony_ci } 6183d0407baSopenharmony_ci mdev_info->devp = mdevp; 6193d0407baSopenharmony_ci cluster->mdev_info = mdev_info; 6203d0407baSopenharmony_ci } else if (event == CPUFREQ_REMOVE_POLICY) { 6213d0407baSopenharmony_ci if (cluster->mdev_info) { 6223d0407baSopenharmony_ci kfree(cluster->mdev_info->devp); 6233d0407baSopenharmony_ci rockchip_system_monitor_unregister(cluster->mdev_info); 6243d0407baSopenharmony_ci cluster->mdev_info = NULL; 6253d0407baSopenharmony_ci } 6263d0407baSopenharmony_ci } 6273d0407baSopenharmony_ci 6283d0407baSopenharmony_ci return NOTIFY_OK; 6293d0407baSopenharmony_ci} 6303d0407baSopenharmony_ci 6313d0407baSopenharmony_cistatic struct notifier_block rockchip_cpufreq_notifier_block = { 6323d0407baSopenharmony_ci .notifier_call = rockchip_cpufreq_notifier, 6333d0407baSopenharmony_ci}; 6343d0407baSopenharmony_ci 6353d0407baSopenharmony_cistatic int __init rockchip_cpufreq_driver_init(void) 6363d0407baSopenharmony_ci{ 6373d0407baSopenharmony_ci struct cluster_info *cluster, *pos; 6383d0407baSopenharmony_ci struct cpufreq_dt_platform_data pdata = {0}; 6393d0407baSopenharmony_ci int cpu, ret; 6403d0407baSopenharmony_ci 6413d0407baSopenharmony_ci for_each_possible_cpu(cpu) { 6423d0407baSopenharmony_ci cluster = rockchip_cluster_info_lookup(cpu); 6433d0407baSopenharmony_ci if (cluster) 6443d0407baSopenharmony_ci continue; 6453d0407baSopenharmony_ci 6463d0407baSopenharmony_ci cluster = kzalloc(sizeof(*cluster), GFP_KERNEL); 6473d0407baSopenharmony_ci if (!cluster) { 6483d0407baSopenharmony_ci ret = -ENOMEM; 6493d0407baSopenharmony_ci goto release_cluster_info; 6503d0407baSopenharmony_ci } 6513d0407baSopenharmony_ci 6523d0407baSopenharmony_ci ret = rockchip_cpufreq_cluster_init(cpu, cluster); 6533d0407baSopenharmony_ci if (ret) { 6543d0407baSopenharmony_ci pr_err("Failed to initialize dvfs info cpu%d\n", cpu); 6553d0407baSopenharmony_ci goto release_cluster_info; 6563d0407baSopenharmony_ci } 6573d0407baSopenharmony_ci list_add(&cluster->list_head, &cluster_info_list); 6583d0407baSopenharmony_ci } 6593d0407baSopenharmony_ci 6603d0407baSopenharmony_ci pdata.have_governor_per_policy = true; 6613d0407baSopenharmony_ci pdata.suspend = rockchip_cpufreq_suspend; 6623d0407baSopenharmony_ci 6633d0407baSopenharmony_ci ret = cpufreq_register_notifier(&rockchip_cpufreq_notifier_block, 6643d0407baSopenharmony_ci CPUFREQ_POLICY_NOTIFIER); 6653d0407baSopenharmony_ci if (ret) { 6663d0407baSopenharmony_ci pr_err("failed to register cpufreq notifier\n"); 6673d0407baSopenharmony_ci goto release_cluster_info; 6683d0407baSopenharmony_ci } 6693d0407baSopenharmony_ci 6703d0407baSopenharmony_ci return PTR_ERR_OR_ZERO(platform_device_register_data(NULL, "cpufreq-dt", 6713d0407baSopenharmony_ci -1, (void *)&pdata, 6723d0407baSopenharmony_ci sizeof(struct cpufreq_dt_platform_data))); 6733d0407baSopenharmony_ci 6743d0407baSopenharmony_cirelease_cluster_info: 6753d0407baSopenharmony_ci list_for_each_entry_safe(cluster, pos, &cluster_info_list, list_head) { 6763d0407baSopenharmony_ci list_del(&cluster->list_head); 6773d0407baSopenharmony_ci kfree(cluster); 6783d0407baSopenharmony_ci } 6793d0407baSopenharmony_ci return ret; 6803d0407baSopenharmony_ci} 6813d0407baSopenharmony_cimodule_init(rockchip_cpufreq_driver_init); 6823d0407baSopenharmony_ci 6833d0407baSopenharmony_ciMODULE_AUTHOR("Finley Xiao <finley.xiao@rock-chips.com>"); 6843d0407baSopenharmony_ciMODULE_DESCRIPTION("Rockchip cpufreq driver"); 6853d0407baSopenharmony_ciMODULE_LICENSE("GPL v2"); 686