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