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