Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

cpufreq: mediatek-hw: Add support for CPUFREQ HW

Introduce cpufreq HW driver which can support
CPU frequency adjust in MT6779 platform.

Signed-off-by: Hector.Yuan <hector.yuan@mediatek.com>
[ Viresh: Massaged the patch and cleaned some stuff. ]
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>

authored by

Hector.Yuan and committed by
Viresh Kumar
4855e26b 8486a32d

+321
+12
drivers/cpufreq/Kconfig.arm
··· 133 133 help 134 134 This adds the CPUFreq driver support for MediaTek SoCs. 135 135 136 + config ARM_MEDIATEK_CPUFREQ_HW 137 + tristate "MediaTek CPUFreq HW driver" 138 + depends on ARCH_MEDIATEK || COMPILE_TEST 139 + default m 140 + help 141 + Support for the CPUFreq HW driver. 142 + Some MediaTek chipsets have a HW engine to offload the steps 143 + necessary for changing the frequency of the CPUs. Firmware loaded 144 + in this engine exposes a programming interface to the OS. 145 + The driver implements the cpufreq interface for this HW engine. 146 + Say Y if you want to support CPUFreq HW. 147 + 136 148 config ARM_OMAP2PLUS_CPUFREQ 137 149 bool "TI OMAP2+" 138 150 depends on ARCH_OMAP2PLUS
+1
drivers/cpufreq/Makefile
··· 56 56 obj-$(CONFIG_ARM_IMX_CPUFREQ_DT) += imx-cpufreq-dt.o 57 57 obj-$(CONFIG_ARM_KIRKWOOD_CPUFREQ) += kirkwood-cpufreq.o 58 58 obj-$(CONFIG_ARM_MEDIATEK_CPUFREQ) += mediatek-cpufreq.o 59 + obj-$(CONFIG_ARM_MEDIATEK_CPUFREQ_HW) += mediatek-cpufreq-hw.o 59 60 obj-$(CONFIG_MACH_MVEBU_V7) += mvebu-cpufreq.o 60 61 obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o 61 62 obj-$(CONFIG_ARM_PXA2xx_CPUFREQ) += pxa2xx-cpufreq.o
+308
drivers/cpufreq/mediatek-cpufreq-hw.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* 3 + * Copyright (c) 2020 MediaTek Inc. 4 + */ 5 + 6 + #include <linux/bitfield.h> 7 + #include <linux/cpufreq.h> 8 + #include <linux/energy_model.h> 9 + #include <linux/init.h> 10 + #include <linux/iopoll.h> 11 + #include <linux/kernel.h> 12 + #include <linux/module.h> 13 + #include <linux/of_address.h> 14 + #include <linux/of_platform.h> 15 + #include <linux/slab.h> 16 + 17 + #define LUT_MAX_ENTRIES 32U 18 + #define LUT_FREQ GENMASK(11, 0) 19 + #define LUT_ROW_SIZE 0x4 20 + #define CPUFREQ_HW_STATUS BIT(0) 21 + #define SVS_HW_STATUS BIT(1) 22 + #define POLL_USEC 1000 23 + #define TIMEOUT_USEC 300000 24 + 25 + enum { 26 + REG_FREQ_LUT_TABLE, 27 + REG_FREQ_ENABLE, 28 + REG_FREQ_PERF_STATE, 29 + REG_FREQ_HW_STATE, 30 + REG_EM_POWER_TBL, 31 + REG_FREQ_LATENCY, 32 + 33 + REG_ARRAY_SIZE, 34 + }; 35 + 36 + struct mtk_cpufreq_data { 37 + struct cpufreq_frequency_table *table; 38 + void __iomem *reg_bases[REG_ARRAY_SIZE]; 39 + int nr_opp; 40 + }; 41 + 42 + static const u16 cpufreq_mtk_offsets[REG_ARRAY_SIZE] = { 43 + [REG_FREQ_LUT_TABLE] = 0x0, 44 + [REG_FREQ_ENABLE] = 0x84, 45 + [REG_FREQ_PERF_STATE] = 0x88, 46 + [REG_FREQ_HW_STATE] = 0x8c, 47 + [REG_EM_POWER_TBL] = 0x90, 48 + [REG_FREQ_LATENCY] = 0x110, 49 + }; 50 + 51 + static int __maybe_unused 52 + mtk_cpufreq_get_cpu_power(unsigned long *mW, 53 + unsigned long *KHz, struct device *cpu_dev) 54 + { 55 + struct mtk_cpufreq_data *data; 56 + struct cpufreq_policy *policy; 57 + int i; 58 + 59 + policy = cpufreq_cpu_get_raw(cpu_dev->id); 60 + if (!policy) 61 + return 0; 62 + 63 + data = policy->driver_data; 64 + 65 + for (i = 0; i < data->nr_opp; i++) { 66 + if (data->table[i].frequency < *KHz) 67 + break; 68 + } 69 + i--; 70 + 71 + *KHz = data->table[i].frequency; 72 + *mW = readl_relaxed(data->reg_bases[REG_EM_POWER_TBL] + 73 + i * LUT_ROW_SIZE) / 1000; 74 + 75 + return 0; 76 + } 77 + 78 + static int mtk_cpufreq_hw_target_index(struct cpufreq_policy *policy, 79 + unsigned int index) 80 + { 81 + struct mtk_cpufreq_data *data = policy->driver_data; 82 + 83 + writel_relaxed(index, data->reg_bases[REG_FREQ_PERF_STATE]); 84 + 85 + return 0; 86 + } 87 + 88 + static unsigned int mtk_cpufreq_hw_get(unsigned int cpu) 89 + { 90 + struct mtk_cpufreq_data *data; 91 + struct cpufreq_policy *policy; 92 + unsigned int index; 93 + 94 + policy = cpufreq_cpu_get_raw(cpu); 95 + if (!policy) 96 + return 0; 97 + 98 + data = policy->driver_data; 99 + 100 + index = readl_relaxed(data->reg_bases[REG_FREQ_PERF_STATE]); 101 + index = min(index, LUT_MAX_ENTRIES - 1); 102 + 103 + return data->table[index].frequency; 104 + } 105 + 106 + static unsigned int mtk_cpufreq_hw_fast_switch(struct cpufreq_policy *policy, 107 + unsigned int target_freq) 108 + { 109 + struct mtk_cpufreq_data *data = policy->driver_data; 110 + unsigned int index; 111 + 112 + index = cpufreq_table_find_index_dl(policy, target_freq); 113 + 114 + writel_relaxed(index, data->reg_bases[REG_FREQ_PERF_STATE]); 115 + 116 + return policy->freq_table[index].frequency; 117 + } 118 + 119 + static int mtk_cpu_create_freq_table(struct platform_device *pdev, 120 + struct mtk_cpufreq_data *data) 121 + { 122 + struct device *dev = &pdev->dev; 123 + u32 temp, i, freq, prev_freq = 0; 124 + void __iomem *base_table; 125 + 126 + data->table = devm_kcalloc(dev, LUT_MAX_ENTRIES + 1, 127 + sizeof(*data->table), GFP_KERNEL); 128 + if (!data->table) 129 + return -ENOMEM; 130 + 131 + base_table = data->reg_bases[REG_FREQ_LUT_TABLE]; 132 + 133 + for (i = 0; i < LUT_MAX_ENTRIES; i++) { 134 + temp = readl_relaxed(base_table + (i * LUT_ROW_SIZE)); 135 + freq = FIELD_GET(LUT_FREQ, temp) * 1000; 136 + 137 + if (freq == prev_freq) 138 + break; 139 + 140 + data->table[i].frequency = freq; 141 + 142 + dev_dbg(dev, "index=%d freq=%d\n", i, data->table[i].frequency); 143 + 144 + prev_freq = freq; 145 + } 146 + 147 + data->table[i].frequency = CPUFREQ_TABLE_END; 148 + data->nr_opp = i; 149 + 150 + return 0; 151 + } 152 + 153 + static int mtk_cpu_resources_init(struct platform_device *pdev, 154 + struct cpufreq_policy *policy, 155 + const u16 *offsets) 156 + { 157 + struct mtk_cpufreq_data *data; 158 + struct device *dev = &pdev->dev; 159 + void __iomem *base; 160 + int ret, i; 161 + int index; 162 + 163 + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 164 + if (!data) 165 + return -ENOMEM; 166 + 167 + index = of_perf_domain_get_sharing_cpumask(policy->cpu, "performance-domains", 168 + "#performance-domain-cells", 169 + policy->cpus); 170 + if (index < 0) 171 + return index; 172 + 173 + base = devm_platform_ioremap_resource(pdev, index); 174 + if (IS_ERR(base)) 175 + return PTR_ERR(base); 176 + 177 + for (i = REG_FREQ_LUT_TABLE; i < REG_ARRAY_SIZE; i++) 178 + data->reg_bases[i] = base + offsets[i]; 179 + 180 + ret = mtk_cpu_create_freq_table(pdev, data); 181 + if (ret) { 182 + dev_info(dev, "Domain-%d failed to create freq table\n", index); 183 + return ret; 184 + } 185 + 186 + policy->freq_table = data->table; 187 + policy->driver_data = data; 188 + 189 + return 0; 190 + } 191 + 192 + static int mtk_cpufreq_hw_cpu_init(struct cpufreq_policy *policy) 193 + { 194 + struct platform_device *pdev = cpufreq_get_driver_data(); 195 + int sig, pwr_hw = CPUFREQ_HW_STATUS | SVS_HW_STATUS; 196 + struct mtk_cpufreq_data *data; 197 + unsigned int latency; 198 + int ret; 199 + 200 + /* Get the bases of cpufreq for domains */ 201 + ret = mtk_cpu_resources_init(pdev, policy, platform_get_drvdata(pdev)); 202 + if (ret) { 203 + dev_info(&pdev->dev, "CPUFreq resource init failed\n"); 204 + return ret; 205 + } 206 + 207 + data = policy->driver_data; 208 + 209 + latency = readl_relaxed(data->reg_bases[REG_FREQ_LATENCY]) * 1000; 210 + if (!latency) 211 + latency = CPUFREQ_ETERNAL; 212 + 213 + policy->cpuinfo.transition_latency = latency; 214 + policy->fast_switch_possible = true; 215 + 216 + /* HW should be in enabled state to proceed now */ 217 + writel_relaxed(0x1, data->reg_bases[REG_FREQ_ENABLE]); 218 + if (readl_poll_timeout(data->reg_bases[REG_FREQ_HW_STATE], sig, 219 + (sig & pwr_hw) == pwr_hw, POLL_USEC, 220 + TIMEOUT_USEC)) { 221 + if (!(sig & CPUFREQ_HW_STATUS)) { 222 + pr_info("cpufreq hardware of CPU%d is not enabled\n", 223 + policy->cpu); 224 + return -ENODEV; 225 + } 226 + 227 + pr_info("SVS of CPU%d is not enabled\n", policy->cpu); 228 + } 229 + 230 + return 0; 231 + } 232 + 233 + static int mtk_cpufreq_hw_cpu_exit(struct cpufreq_policy *policy) 234 + { 235 + struct mtk_cpufreq_data *data = policy->driver_data; 236 + 237 + /* HW should be in paused state now */ 238 + writel_relaxed(0x0, data->reg_bases[REG_FREQ_ENABLE]); 239 + 240 + return 0; 241 + } 242 + 243 + static void mtk_cpufreq_register_em(struct cpufreq_policy *policy) 244 + { 245 + struct em_data_callback em_cb = EM_DATA_CB(mtk_cpufreq_get_cpu_power); 246 + struct mtk_cpufreq_data *data = policy->driver_data; 247 + 248 + em_dev_register_perf_domain(get_cpu_device(policy->cpu), data->nr_opp, 249 + &em_cb, policy->cpus, true); 250 + } 251 + 252 + static struct cpufreq_driver cpufreq_mtk_hw_driver = { 253 + .flags = CPUFREQ_NEED_INITIAL_FREQ_CHECK | 254 + CPUFREQ_HAVE_GOVERNOR_PER_POLICY | 255 + CPUFREQ_IS_COOLING_DEV, 256 + .verify = cpufreq_generic_frequency_table_verify, 257 + .target_index = mtk_cpufreq_hw_target_index, 258 + .get = mtk_cpufreq_hw_get, 259 + .init = mtk_cpufreq_hw_cpu_init, 260 + .exit = mtk_cpufreq_hw_cpu_exit, 261 + .register_em = mtk_cpufreq_register_em, 262 + .fast_switch = mtk_cpufreq_hw_fast_switch, 263 + .name = "mtk-cpufreq-hw", 264 + .attr = cpufreq_generic_attr, 265 + }; 266 + 267 + static int mtk_cpufreq_hw_driver_probe(struct platform_device *pdev) 268 + { 269 + const void *data; 270 + int ret; 271 + 272 + data = of_device_get_match_data(&pdev->dev); 273 + if (!data) 274 + return -EINVAL; 275 + 276 + platform_set_drvdata(pdev, (void *) data); 277 + cpufreq_mtk_hw_driver.driver_data = pdev; 278 + 279 + ret = cpufreq_register_driver(&cpufreq_mtk_hw_driver); 280 + if (ret) 281 + dev_err(&pdev->dev, "CPUFreq HW driver failed to register\n"); 282 + 283 + return ret; 284 + } 285 + 286 + static int mtk_cpufreq_hw_driver_remove(struct platform_device *pdev) 287 + { 288 + return cpufreq_unregister_driver(&cpufreq_mtk_hw_driver); 289 + } 290 + 291 + static const struct of_device_id mtk_cpufreq_hw_match[] = { 292 + { .compatible = "mediatek,cpufreq-hw", .data = &cpufreq_mtk_offsets }, 293 + {} 294 + }; 295 + 296 + static struct platform_driver mtk_cpufreq_hw_driver = { 297 + .probe = mtk_cpufreq_hw_driver_probe, 298 + .remove = mtk_cpufreq_hw_driver_remove, 299 + .driver = { 300 + .name = "mtk-cpufreq-hw", 301 + .of_match_table = mtk_cpufreq_hw_match, 302 + }, 303 + }; 304 + module_platform_driver(mtk_cpufreq_hw_driver); 305 + 306 + MODULE_AUTHOR("Hector Yuan <hector.yuan@mediatek.com>"); 307 + MODULE_DESCRIPTION("Mediatek cpufreq-hw driver"); 308 + MODULE_LICENSE("GPL v2");