Abhijeet Dharmapurikar | 4445166 | 2012-08-23 18:58:44 -0700 | [diff] [blame] | 1 | /* Copyright (c) 2012, The Linux Foundation. All rights reserved. |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 2 | * |
| 3 | * This program is free software; you can redistribute it and/or modify |
| 4 | * it under the terms of the GNU General Public License version 2 and |
| 5 | * only version 2 as published by the Free Software Foundation. |
| 6 | * |
| 7 | * This program is distributed in the hope that it will be useful, |
| 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 10 | * GNU General Public License for more details. |
| 11 | * |
| 12 | */ |
| 13 | |
| 14 | #include <linux/kernel.h> |
| 15 | #include <linux/init.h> |
| 16 | #include <linux/module.h> |
| 17 | #include <linux/mutex.h> |
| 18 | #include <linux/kobject.h> |
| 19 | #include <linux/cpufreq.h> |
| 20 | #include <linux/platform_device.h> |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 21 | #include <linux/cpu_pm.h> |
| 22 | #include <linux/pm_qos.h> |
| 23 | #include <linux/hrtimer.h> |
| 24 | #include <linux/tick.h> |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 25 | #include <mach/msm_dcvs.h> |
| 26 | |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 27 | struct cpu_idle_info { |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 28 | int enabled; |
| 29 | int dcvs_core_id; |
| 30 | struct pm_qos_request pm_qos_req; |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 31 | }; |
| 32 | |
| 33 | static DEFINE_PER_CPU_SHARED_ALIGNED(struct cpu_idle_info, cpu_idle_info); |
| 34 | static DEFINE_PER_CPU_SHARED_ALIGNED(u64, iowait_on_cpu); |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 35 | static uint32_t latency; |
| 36 | |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 37 | static int msm_dcvs_idle_notifier(int core_num, |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 38 | enum msm_core_control_event event) |
| 39 | { |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 40 | struct cpu_idle_info *info = &per_cpu(cpu_idle_info, core_num); |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 41 | |
| 42 | switch (event) { |
| 43 | case MSM_DCVS_ENABLE_IDLE_PULSE: |
| 44 | info->enabled = true; |
| 45 | break; |
| 46 | |
| 47 | case MSM_DCVS_DISABLE_IDLE_PULSE: |
| 48 | info->enabled = false; |
| 49 | break; |
| 50 | |
| 51 | case MSM_DCVS_ENABLE_HIGH_LATENCY_MODES: |
| 52 | pm_qos_update_request(&info->pm_qos_req, PM_QOS_DEFAULT_VALUE); |
| 53 | break; |
| 54 | |
| 55 | case MSM_DCVS_DISABLE_HIGH_LATENCY_MODES: |
| 56 | pm_qos_update_request(&info->pm_qos_req, latency); |
| 57 | break; |
| 58 | } |
| 59 | |
| 60 | return 0; |
| 61 | } |
| 62 | |
| 63 | static int msm_cpuidle_notifier(struct notifier_block *self, unsigned long cmd, |
| 64 | void *v) |
| 65 | { |
| 66 | struct cpu_idle_info *info = |
| 67 | &per_cpu(cpu_idle_info, smp_processor_id()); |
| 68 | u64 io_wait_us = 0; |
| 69 | u64 prev_io_wait_us = 0; |
| 70 | u64 last_update_time = 0; |
| 71 | u64 val = 0; |
| 72 | uint32_t iowaited = 0; |
| 73 | |
| 74 | if (!info->enabled) |
| 75 | return NOTIFY_OK; |
| 76 | |
| 77 | switch (cmd) { |
| 78 | case CPU_PM_ENTER: |
| 79 | val = get_cpu_iowait_time_us(smp_processor_id(), |
| 80 | &last_update_time); |
| 81 | /* val could be -1 when NOHZ is not enabled */ |
| 82 | if (val == (u64)-1) |
| 83 | val = 0; |
| 84 | per_cpu(iowait_on_cpu, smp_processor_id()) = val; |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 85 | msm_dcvs_idle(info->dcvs_core_id, MSM_DCVS_IDLE_ENTER, 0); |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 86 | break; |
| 87 | |
| 88 | case CPU_PM_EXIT: |
| 89 | prev_io_wait_us = per_cpu(iowait_on_cpu, smp_processor_id()); |
| 90 | val = get_cpu_iowait_time_us(smp_processor_id(), |
| 91 | &last_update_time); |
| 92 | if (val == (u64)-1) |
| 93 | val = 0; |
| 94 | io_wait_us = val; |
| 95 | iowaited = (io_wait_us - prev_io_wait_us); |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 96 | msm_dcvs_idle(info->dcvs_core_id, MSM_DCVS_IDLE_EXIT, iowaited); |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 97 | break; |
| 98 | } |
| 99 | |
| 100 | return NOTIFY_OK; |
| 101 | } |
| 102 | |
| 103 | static struct notifier_block idle_nb = { |
| 104 | .notifier_call = msm_cpuidle_notifier, |
| 105 | }; |
| 106 | |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 107 | static void msm_gov_idle_source_init(int cpu, int dcvs_core_id) |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 108 | { |
| 109 | struct cpu_idle_info *info = NULL; |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 110 | |
| 111 | info = &per_cpu(cpu_idle_info, cpu); |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 112 | info->dcvs_core_id = dcvs_core_id; |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 113 | |
| 114 | pm_qos_add_request(&info->pm_qos_req, PM_QOS_CPU_DMA_LATENCY, |
| 115 | PM_QOS_DEFAULT_VALUE); |
| 116 | } |
| 117 | |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 118 | struct msm_gov { |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 119 | int cpu; |
| 120 | unsigned int cur_freq; |
| 121 | unsigned int min_freq; |
| 122 | unsigned int max_freq; |
| 123 | struct cpufreq_policy *policy; |
| 124 | int dcvs_core_id; |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 125 | }; |
| 126 | |
| 127 | static DEFINE_PER_CPU_SHARED_ALIGNED(struct mutex, gov_mutex); |
| 128 | static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_gov, msm_gov_info); |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 129 | |
| 130 | static void msm_gov_check_limits(struct cpufreq_policy *policy) |
| 131 | { |
| 132 | struct msm_gov *gov = &per_cpu(msm_gov_info, policy->cpu); |
| 133 | |
| 134 | if (policy->max < gov->cur_freq) |
| 135 | __cpufreq_driver_target(policy, policy->max, |
| 136 | CPUFREQ_RELATION_H); |
Abhijeet Dharmapurikar | 68c970e | 2012-08-31 20:42:53 -0700 | [diff] [blame] | 137 | else if (policy->min > gov->cur_freq) |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 138 | __cpufreq_driver_target(policy, policy->min, |
| 139 | CPUFREQ_RELATION_L); |
| 140 | else |
| 141 | __cpufreq_driver_target(policy, gov->cur_freq, |
| 142 | CPUFREQ_RELATION_L); |
| 143 | |
| 144 | gov->cur_freq = policy->cur; |
| 145 | gov->min_freq = policy->min; |
| 146 | gov->max_freq = policy->max; |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 147 | msm_dcvs_update_limits(gov->dcvs_core_id); |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 148 | } |
| 149 | |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 150 | static int msm_dcvs_freq_set(int core_num, |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 151 | unsigned int freq) |
| 152 | { |
| 153 | int ret = -EINVAL; |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 154 | struct msm_gov *gov = &per_cpu(msm_gov_info, core_num); |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 155 | |
| 156 | mutex_lock(&per_cpu(gov_mutex, gov->cpu)); |
| 157 | |
| 158 | if (freq < gov->min_freq) |
| 159 | freq = gov->min_freq; |
| 160 | if (freq > gov->max_freq) |
| 161 | freq = gov->max_freq; |
| 162 | |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 163 | mutex_unlock(&per_cpu(gov_mutex, gov->cpu)); |
| 164 | |
Abhijeet Dharmapurikar | 54fc08e | 2012-08-26 20:33:43 -0700 | [diff] [blame] | 165 | ret = cpufreq_driver_target(gov->policy, freq, CPUFREQ_RELATION_L); |
| 166 | |
| 167 | if (!ret) { |
| 168 | gov->cur_freq = cpufreq_quick_get(gov->cpu); |
| 169 | if (freq != gov->cur_freq) |
| 170 | pr_err("cpu %d freq %u gov->cur_freq %u didn't match", |
| 171 | gov->cpu, freq, gov->cur_freq); |
| 172 | } |
| 173 | ret = gov->cur_freq; |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 174 | |
| 175 | return ret; |
| 176 | } |
| 177 | |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 178 | static unsigned int msm_dcvs_freq_get(int core_num) |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 179 | { |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 180 | struct msm_gov *gov = &per_cpu(msm_gov_info, core_num); |
Abhijeet Dharmapurikar | 54fc08e | 2012-08-26 20:33:43 -0700 | [diff] [blame] | 181 | /* |
| 182 | * the rw_sem in cpufreq is always held when this is called. |
| 183 | * The policy->cur won't be updated in this case - so it is safe to |
| 184 | * access policy->cur |
| 185 | */ |
Abhijeet Dharmapurikar | 68c970e | 2012-08-31 20:42:53 -0700 | [diff] [blame] | 186 | return gov->policy->cur; |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 187 | } |
| 188 | |
| 189 | static int cpufreq_governor_msm(struct cpufreq_policy *policy, |
| 190 | unsigned int event) |
| 191 | { |
| 192 | unsigned int cpu = policy->cpu; |
| 193 | int ret = 0; |
| 194 | int handle = 0; |
| 195 | struct msm_gov *gov = &per_cpu(msm_gov_info, policy->cpu); |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 196 | |
| 197 | switch (event) { |
| 198 | case CPUFREQ_GOV_START: |
| 199 | if (!cpu_online(cpu)) |
| 200 | return -EINVAL; |
| 201 | BUG_ON(!policy->cur); |
| 202 | mutex_lock(&per_cpu(gov_mutex, cpu)); |
| 203 | per_cpu(msm_gov_info, cpu).cpu = cpu; |
| 204 | gov->policy = policy; |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 205 | handle = msm_dcvs_freq_sink_start(gov->dcvs_core_id); |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 206 | BUG_ON(handle < 0); |
| 207 | msm_gov_check_limits(policy); |
| 208 | mutex_unlock(&per_cpu(gov_mutex, cpu)); |
| 209 | break; |
| 210 | |
| 211 | case CPUFREQ_GOV_STOP: |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 212 | msm_dcvs_freq_sink_stop(gov->dcvs_core_id); |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 213 | break; |
| 214 | |
| 215 | case CPUFREQ_GOV_LIMITS: |
| 216 | mutex_lock(&per_cpu(gov_mutex, cpu)); |
| 217 | msm_gov_check_limits(policy); |
| 218 | mutex_unlock(&per_cpu(gov_mutex, cpu)); |
| 219 | break; |
| 220 | }; |
| 221 | |
| 222 | return ret; |
| 223 | } |
| 224 | |
| 225 | struct cpufreq_governor cpufreq_gov_msm = { |
| 226 | .name = "msm-dcvs", |
| 227 | .governor = cpufreq_governor_msm, |
| 228 | .owner = THIS_MODULE, |
| 229 | }; |
| 230 | |
| 231 | static int __devinit msm_gov_probe(struct platform_device *pdev) |
| 232 | { |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 233 | int cpu; |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 234 | struct msm_dcvs_core_info *core = NULL; |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 235 | struct msm_dcvs_core_info *core_info = NULL; |
| 236 | struct msm_gov_platform_data *pdata = pdev->dev.platform_data; |
Abhijeet Dharmapurikar | fc7dca4 | 2012-08-26 18:27:53 -0700 | [diff] [blame] | 237 | int sensor = 0; |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 238 | |
| 239 | core = pdev->dev.platform_data; |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 240 | core_info = pdata->info; |
| 241 | latency = pdata->latency; |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 242 | |
| 243 | for_each_possible_cpu(cpu) { |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 244 | struct msm_gov *gov = &per_cpu(msm_gov_info, cpu); |
| 245 | |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 246 | mutex_init(&per_cpu(gov_mutex, cpu)); |
Abhijeet Dharmapurikar | fc7dca4 | 2012-08-26 18:27:53 -0700 | [diff] [blame] | 247 | if (cpu < core->num_cores) |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 248 | sensor = core_info->sensors[cpu]; |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 249 | gov->dcvs_core_id = msm_dcvs_register_core( |
| 250 | MSM_DCVS_CORE_TYPE_CPU, |
| 251 | cpu, |
| 252 | core_info, |
Abhijeet Dharmapurikar | 6913411 | 2012-08-31 22:10:41 -0700 | [diff] [blame] | 253 | msm_dcvs_freq_set, |
| 254 | msm_dcvs_freq_get, |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 255 | msm_dcvs_idle_notifier, |
Steve Muckle | 682c7a0 | 2012-11-12 14:20:39 -0800 | [diff] [blame] | 256 | NULL, |
Abhijeet Dharmapurikar | 6913411 | 2012-08-31 22:10:41 -0700 | [diff] [blame] | 257 | sensor); |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 258 | if (gov->dcvs_core_id < 0) { |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 259 | pr_err("Unable to register core for %d\n", cpu); |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 260 | return -EINVAL; |
| 261 | } |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 262 | |
Abhijeet Dharmapurikar | 1bbc032 | 2012-09-12 16:40:20 -0700 | [diff] [blame] | 263 | msm_gov_idle_source_init(cpu, gov->dcvs_core_id); |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 264 | } |
| 265 | |
Abhijeet Dharmapurikar | c1ed66c | 2012-09-10 16:03:39 -0700 | [diff] [blame] | 266 | cpu_pm_register_notifier(&idle_nb); |
| 267 | |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 268 | return cpufreq_register_governor(&cpufreq_gov_msm); |
| 269 | } |
| 270 | |
| 271 | static int __devexit msm_gov_remove(struct platform_device *pdev) |
| 272 | { |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 273 | platform_set_drvdata(pdev, NULL); |
| 274 | return 0; |
| 275 | } |
| 276 | |
| 277 | static struct platform_driver msm_gov_driver = { |
| 278 | .probe = msm_gov_probe, |
| 279 | .remove = __devexit_p(msm_gov_remove), |
| 280 | .driver = { |
| 281 | .name = "msm_dcvs_gov", |
| 282 | .owner = THIS_MODULE, |
| 283 | }, |
| 284 | }; |
| 285 | |
| 286 | static int __init cpufreq_gov_msm_init(void) |
| 287 | { |
| 288 | return platform_driver_register(&msm_gov_driver); |
| 289 | } |
| 290 | late_initcall(cpufreq_gov_msm_init); |