1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (c) 2019 Samsung Electronics Co., Ltd. 4 * http://www.samsung.com/ 5 * Copyright (c) 2020 Krzysztof Kozlowski <krzk@kernel.org> 6 * Author: Sylwester Nawrocki <s.nawrocki@samsung.com> 7 * Author: Krzysztof Kozlowski <krzk@kernel.org> 8 * 9 * Samsung Exynos SoC Adaptive Supply Voltage support 10 */ 11 12 #include <linux/array_size.h> 13 #include <linux/cpu.h> 14 #include <linux/device.h> 15 #include <linux/energy_model.h> 16 #include <linux/errno.h> 17 #include <linux/of.h> 18 #include <linux/pm_opp.h> 19 #include <linux/regmap.h> 20 #include <linux/soc/samsung/exynos-chipid.h> 21 22 #include "exynos-asv.h" 23 #include "exynos5422-asv.h" 24 25 #define MHZ 1000000U 26 27 static int exynos_asv_update_cpu_opps(struct exynos_asv *asv, 28 struct device *cpu) 29 { 30 struct exynos_asv_subsys *subsys = NULL; 31 struct dev_pm_opp *opp; 32 unsigned int opp_freq; 33 int i; 34 35 for (i = 0; i < ARRAY_SIZE(asv->subsys); i++) { 36 if (of_device_is_compatible(cpu->of_node, 37 asv->subsys[i].cpu_dt_compat)) { 38 subsys = &asv->subsys[i]; 39 break; 40 } 41 } 42 if (!subsys) 43 return -EINVAL; 44 45 for (i = 0; i < subsys->table.num_rows; i++) { 46 unsigned int new_volt, volt; 47 int ret; 48 49 opp_freq = exynos_asv_opp_get_frequency(subsys, i); 50 51 opp = dev_pm_opp_find_freq_exact(cpu, opp_freq * MHZ, true); 52 if (IS_ERR(opp)) { 53 dev_info(asv->dev, "cpu%d opp%d, freq: %u missing\n", 54 cpu->id, i, opp_freq); 55 56 continue; 57 } 58 59 volt = dev_pm_opp_get_voltage(opp); 60 new_volt = asv->opp_get_voltage(subsys, i, volt); 61 dev_pm_opp_put(opp); 62 63 if (new_volt == volt) 64 continue; 65 66 ret = dev_pm_opp_adjust_voltage(cpu, opp_freq * MHZ, 67 new_volt, new_volt, new_volt); 68 if (ret < 0) 69 dev_err(asv->dev, 70 "Failed to adjust OPP %u Hz/%u uV for cpu%d\n", 71 opp_freq, new_volt, cpu->id); 72 else 73 dev_dbg(asv->dev, 74 "Adjusted OPP %u Hz/%u -> %u uV, cpu%d\n", 75 opp_freq, volt, new_volt, cpu->id); 76 } 77 78 return 0; 79 } 80 81 static int exynos_asv_update_opps(struct exynos_asv *asv) 82 { 83 struct opp_table *last_opp_table = NULL; 84 struct device *cpu; 85 int ret, cpuid; 86 87 for_each_possible_cpu(cpuid) { 88 struct opp_table *opp_table; 89 90 cpu = get_cpu_device(cpuid); 91 if (!cpu) 92 continue; 93 94 opp_table = dev_pm_opp_get_opp_table(cpu); 95 if (IS_ERR(opp_table)) 96 continue; 97 98 if (!last_opp_table || opp_table != last_opp_table) { 99 last_opp_table = opp_table; 100 101 ret = exynos_asv_update_cpu_opps(asv, cpu); 102 if (!ret) { 103 /* 104 * Update EM power values since OPP 105 * voltage values may have changed. 106 */ 107 em_dev_update_chip_binning(cpu); 108 } else { 109 dev_err(asv->dev, "Couldn't udate OPPs for cpu%d\n", 110 cpuid); 111 } 112 } 113 114 dev_pm_opp_put_opp_table(opp_table); 115 } 116 117 return 0; 118 } 119 120 int exynos_asv_init(struct device *dev, struct regmap *regmap) 121 { 122 int (*probe_func)(struct exynos_asv *asv); 123 struct exynos_asv *asv; 124 struct device *cpu_dev; 125 u32 product_id = 0; 126 int ret, i; 127 128 asv = devm_kzalloc(dev, sizeof(*asv), GFP_KERNEL); 129 if (!asv) 130 return -ENOMEM; 131 132 asv->chipid_regmap = regmap; 133 asv->dev = dev; 134 ret = regmap_read(asv->chipid_regmap, EXYNOS_CHIPID_REG_PRO_ID, 135 &product_id); 136 if (ret < 0) { 137 dev_err(dev, "Cannot read revision from ChipID: %d\n", ret); 138 return -ENODEV; 139 } 140 141 switch (product_id & EXYNOS_MASK) { 142 case 0xE5422000: 143 probe_func = exynos5422_asv_init; 144 break; 145 default: 146 dev_dbg(dev, "No ASV support for this SoC\n"); 147 devm_kfree(dev, asv); 148 return 0; 149 } 150 151 cpu_dev = get_cpu_device(0); 152 ret = dev_pm_opp_get_opp_count(cpu_dev); 153 if (ret < 0) 154 return -EPROBE_DEFER; 155 156 ret = of_property_read_u32(dev->of_node, "samsung,asv-bin", 157 &asv->of_bin); 158 if (ret < 0) 159 asv->of_bin = -EINVAL; 160 161 for (i = 0; i < ARRAY_SIZE(asv->subsys); i++) 162 asv->subsys[i].asv = asv; 163 164 ret = probe_func(asv); 165 if (ret < 0) 166 return ret; 167 168 return exynos_asv_update_opps(asv); 169 } 170