Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | /* * CPPC (Collaborative Processor Performance Control) driver for * interfacing with the CPUfreq layer and governors. See * cppc_acpi.c for CPPC specific methods. * * (C) Copyright 2014, 2015 Linaro Ltd. * Author: Ashwin Chaugule <ashwin.chaugule@linaro.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; version 2 * of the License. */ #define pr_fmt(fmt) "CPPC Cpufreq:" fmt #include <linux/kernel.h> #include <linux/module.h> #include <linux/delay.h> #include <linux/cpu.h> #include <linux/cpufreq.h> #include <linux/vmalloc.h> #include <acpi/cppc_acpi.h> /* * These structs contain information parsed from per CPU * ACPI _CPC structures. * e.g. For each CPU the highest, lowest supported * performance capabilities, desired performance level * requested etc. */ static struct cpudata **all_cpu_data; static int cppc_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int target_freq, unsigned int relation) { struct cpudata *cpu; struct cpufreq_freqs freqs; int ret = 0; cpu = all_cpu_data[policy->cpu]; cpu->perf_ctrls.desired_perf = target_freq; freqs.old = policy->cur; freqs.new = target_freq; cpufreq_freq_transition_begin(policy, &freqs); ret = cppc_set_perf(cpu->cpu, &cpu->perf_ctrls); cpufreq_freq_transition_end(policy, &freqs, ret != 0); if (ret) pr_debug("Failed to set target on CPU:%d. ret:%d\n", cpu->cpu, ret); return ret; } static int cppc_verify_policy(struct cpufreq_policy *policy) { cpufreq_verify_within_cpu_limits(policy); return 0; } static void cppc_cpufreq_stop_cpu(struct cpufreq_policy *policy) { int cpu_num = policy->cpu; struct cpudata *cpu = all_cpu_data[cpu_num]; int ret; cpu->perf_ctrls.desired_perf = cpu->perf_caps.lowest_perf; ret = cppc_set_perf(cpu_num, &cpu->perf_ctrls); if (ret) pr_debug("Err setting perf value:%d on CPU:%d. ret:%d\n", cpu->perf_caps.lowest_perf, cpu_num, ret); } static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy) { struct cpudata *cpu; unsigned int cpu_num = policy->cpu; int ret = 0; cpu = all_cpu_data[policy->cpu]; cpu->cpu = cpu_num; ret = cppc_get_perf_caps(policy->cpu, &cpu->perf_caps); if (ret) { pr_debug("Err reading CPU%d perf capabilities. ret:%d\n", cpu_num, ret); return ret; } policy->min = cpu->perf_caps.lowest_perf; policy->max = cpu->perf_caps.highest_perf; policy->cpuinfo.min_freq = policy->min; policy->cpuinfo.max_freq = policy->max; policy->shared_type = cpu->shared_type; if (policy->shared_type == CPUFREQ_SHARED_TYPE_ANY) { int i; cpumask_copy(policy->cpus, cpu->shared_cpu_map); for_each_cpu(i, policy->cpus) { if (unlikely(i == policy->cpu)) continue; memcpy(&all_cpu_data[i]->perf_caps, &cpu->perf_caps, sizeof(cpu->perf_caps)); } } else if (policy->shared_type == CPUFREQ_SHARED_TYPE_ALL) { /* Support only SW_ANY for now. */ pr_debug("Unsupported CPU co-ord type\n"); return -EFAULT; } cpumask_set_cpu(policy->cpu, policy->cpus); cpu->cur_policy = policy; /* Set policy->cur to max now. The governors will adjust later. */ policy->cur = cpu->perf_ctrls.desired_perf = cpu->perf_caps.highest_perf; ret = cppc_set_perf(cpu_num, &cpu->perf_ctrls); if (ret) pr_debug("Err setting perf value:%d on CPU:%d. ret:%d\n", cpu->perf_caps.highest_perf, cpu_num, ret); return ret; } static struct cpufreq_driver cppc_cpufreq_driver = { .flags = CPUFREQ_CONST_LOOPS, .verify = cppc_verify_policy, .target = cppc_cpufreq_set_target, .init = cppc_cpufreq_cpu_init, .stop_cpu = cppc_cpufreq_stop_cpu, .name = "cppc_cpufreq", }; static int __init cppc_cpufreq_init(void) { int i, ret = 0; struct cpudata *cpu; if (acpi_disabled) return -ENODEV; all_cpu_data = kzalloc(sizeof(void *) * num_possible_cpus(), GFP_KERNEL); if (!all_cpu_data) return -ENOMEM; for_each_possible_cpu(i) { all_cpu_data[i] = kzalloc(sizeof(struct cpudata), GFP_KERNEL); if (!all_cpu_data[i]) goto out; cpu = all_cpu_data[i]; if (!zalloc_cpumask_var(&cpu->shared_cpu_map, GFP_KERNEL)) goto out; } ret = acpi_get_psd_map(all_cpu_data); if (ret) { pr_debug("Error parsing PSD data. Aborting cpufreq registration.\n"); goto out; } ret = cpufreq_register_driver(&cppc_cpufreq_driver); if (ret) goto out; return ret; out: for_each_possible_cpu(i) { cpu = all_cpu_data[i]; if (!cpu) break; free_cpumask_var(cpu->shared_cpu_map); kfree(cpu); } kfree(all_cpu_data); return -ENODEV; } late_initcall(cppc_cpufreq_init); |