Abhijeet Dharmapurikar | 7e37e6e | 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 | 6e9b34f | 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 | 6e9b34f | 2012-09-10 16:03:39 -0700 | [diff] [blame^] | 27 | struct cpu_idle_info { |
| 28 | int cpu; |
| 29 | int enabled; |
| 30 | int handle; |
| 31 | struct msm_dcvs_idle dcvs_notifier; |
| 32 | struct pm_qos_request pm_qos_req; |
| 33 | }; |
| 34 | |
| 35 | static DEFINE_PER_CPU_SHARED_ALIGNED(struct cpu_idle_info, cpu_idle_info); |
| 36 | static DEFINE_PER_CPU_SHARED_ALIGNED(u64, iowait_on_cpu); |
| 37 | static char core_name[NR_CPUS][10]; |
| 38 | static uint32_t latency; |
| 39 | |
| 40 | static int msm_dcvs_idle_notifier(struct msm_dcvs_idle *self, |
| 41 | enum msm_core_control_event event) |
| 42 | { |
| 43 | struct cpu_idle_info *info = container_of(self, |
| 44 | struct cpu_idle_info, dcvs_notifier); |
| 45 | |
| 46 | switch (event) { |
| 47 | case MSM_DCVS_ENABLE_IDLE_PULSE: |
| 48 | info->enabled = true; |
| 49 | break; |
| 50 | |
| 51 | case MSM_DCVS_DISABLE_IDLE_PULSE: |
| 52 | info->enabled = false; |
| 53 | break; |
| 54 | |
| 55 | case MSM_DCVS_ENABLE_HIGH_LATENCY_MODES: |
| 56 | pm_qos_update_request(&info->pm_qos_req, PM_QOS_DEFAULT_VALUE); |
| 57 | break; |
| 58 | |
| 59 | case MSM_DCVS_DISABLE_HIGH_LATENCY_MODES: |
| 60 | pm_qos_update_request(&info->pm_qos_req, latency); |
| 61 | break; |
| 62 | } |
| 63 | |
| 64 | return 0; |
| 65 | } |
| 66 | |
| 67 | static int msm_cpuidle_notifier(struct notifier_block *self, unsigned long cmd, |
| 68 | void *v) |
| 69 | { |
| 70 | struct cpu_idle_info *info = |
| 71 | &per_cpu(cpu_idle_info, smp_processor_id()); |
| 72 | u64 io_wait_us = 0; |
| 73 | u64 prev_io_wait_us = 0; |
| 74 | u64 last_update_time = 0; |
| 75 | u64 val = 0; |
| 76 | uint32_t iowaited = 0; |
| 77 | |
| 78 | if (!info->enabled) |
| 79 | return NOTIFY_OK; |
| 80 | |
| 81 | switch (cmd) { |
| 82 | case CPU_PM_ENTER: |
| 83 | val = get_cpu_iowait_time_us(smp_processor_id(), |
| 84 | &last_update_time); |
| 85 | /* val could be -1 when NOHZ is not enabled */ |
| 86 | if (val == (u64)-1) |
| 87 | val = 0; |
| 88 | per_cpu(iowait_on_cpu, smp_processor_id()) = val; |
| 89 | msm_dcvs_idle(info->handle, MSM_DCVS_IDLE_ENTER, 0); |
| 90 | break; |
| 91 | |
| 92 | case CPU_PM_EXIT: |
| 93 | prev_io_wait_us = per_cpu(iowait_on_cpu, smp_processor_id()); |
| 94 | val = get_cpu_iowait_time_us(smp_processor_id(), |
| 95 | &last_update_time); |
| 96 | if (val == (u64)-1) |
| 97 | val = 0; |
| 98 | io_wait_us = val; |
| 99 | iowaited = (io_wait_us - prev_io_wait_us); |
| 100 | msm_dcvs_idle(info->handle, MSM_DCVS_IDLE_EXIT, iowaited); |
| 101 | break; |
| 102 | } |
| 103 | |
| 104 | return NOTIFY_OK; |
| 105 | } |
| 106 | |
| 107 | static struct notifier_block idle_nb = { |
| 108 | .notifier_call = msm_cpuidle_notifier, |
| 109 | }; |
| 110 | |
| 111 | static void msm_gov_idle_source_init(int cpu) |
| 112 | { |
| 113 | struct cpu_idle_info *info = NULL; |
| 114 | struct msm_dcvs_idle *inotify = NULL; |
| 115 | |
| 116 | info = &per_cpu(cpu_idle_info, cpu); |
| 117 | info->cpu = cpu; |
| 118 | inotify = &info->dcvs_notifier; |
| 119 | snprintf(core_name[cpu], 10, "cpu%d", cpu); |
| 120 | inotify->core_name = core_name[cpu]; |
| 121 | info->handle = msm_dcvs_idle_source_register(inotify); |
| 122 | BUG_ON(info->handle < 0); |
| 123 | |
| 124 | pm_qos_add_request(&info->pm_qos_req, PM_QOS_CPU_DMA_LATENCY, |
| 125 | PM_QOS_DEFAULT_VALUE); |
| 126 | } |
| 127 | |
| 128 | static int msm_gov_idle_source_uninit(int cpu) |
| 129 | { |
| 130 | struct cpu_idle_info *info = NULL; |
| 131 | struct msm_dcvs_idle *inotify = NULL; |
| 132 | |
| 133 | info = &per_cpu(cpu_idle_info, cpu); |
| 134 | info->cpu = cpu; |
| 135 | inotify = &info->dcvs_notifier; |
| 136 | return msm_dcvs_idle_source_unregister(inotify); |
| 137 | } |
| 138 | |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 139 | struct msm_gov { |
| 140 | int cpu; |
| 141 | unsigned int cur_freq; |
| 142 | unsigned int min_freq; |
| 143 | unsigned int max_freq; |
| 144 | struct msm_dcvs_freq gov_notifier; |
| 145 | struct cpufreq_policy *policy; |
| 146 | }; |
| 147 | |
| 148 | static DEFINE_PER_CPU_SHARED_ALIGNED(struct mutex, gov_mutex); |
| 149 | static DEFINE_PER_CPU_SHARED_ALIGNED(struct msm_gov, msm_gov_info); |
| 150 | static char core_name[NR_CPUS][10]; |
| 151 | |
| 152 | static void msm_gov_check_limits(struct cpufreq_policy *policy) |
| 153 | { |
| 154 | struct msm_gov *gov = &per_cpu(msm_gov_info, policy->cpu); |
Abhijeet Dharmapurikar | c43f0db | 2012-08-31 20:42:53 -0700 | [diff] [blame] | 155 | struct msm_dcvs_freq *dcvs_notifier = |
| 156 | &(per_cpu(msm_gov_info, policy->cpu).gov_notifier); |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 157 | |
| 158 | if (policy->max < gov->cur_freq) |
| 159 | __cpufreq_driver_target(policy, policy->max, |
| 160 | CPUFREQ_RELATION_H); |
Abhijeet Dharmapurikar | c43f0db | 2012-08-31 20:42:53 -0700 | [diff] [blame] | 161 | else if (policy->min > gov->cur_freq) |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 162 | __cpufreq_driver_target(policy, policy->min, |
| 163 | CPUFREQ_RELATION_L); |
| 164 | else |
| 165 | __cpufreq_driver_target(policy, gov->cur_freq, |
| 166 | CPUFREQ_RELATION_L); |
| 167 | |
| 168 | gov->cur_freq = policy->cur; |
| 169 | gov->min_freq = policy->min; |
| 170 | gov->max_freq = policy->max; |
Abhijeet Dharmapurikar | c43f0db | 2012-08-31 20:42:53 -0700 | [diff] [blame] | 171 | msm_dcvs_update_limits(dcvs_notifier); |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 172 | } |
| 173 | |
| 174 | static int msm_dcvs_freq_set(struct msm_dcvs_freq *self, |
| 175 | unsigned int freq) |
| 176 | { |
| 177 | int ret = -EINVAL; |
| 178 | struct msm_gov *gov = |
| 179 | container_of(self, struct msm_gov, gov_notifier); |
| 180 | |
| 181 | mutex_lock(&per_cpu(gov_mutex, gov->cpu)); |
| 182 | |
| 183 | if (freq < gov->min_freq) |
| 184 | freq = gov->min_freq; |
| 185 | if (freq > gov->max_freq) |
| 186 | freq = gov->max_freq; |
| 187 | |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 188 | mutex_unlock(&per_cpu(gov_mutex, gov->cpu)); |
| 189 | |
Abhijeet Dharmapurikar | 83347e0 | 2012-08-26 20:33:43 -0700 | [diff] [blame] | 190 | ret = cpufreq_driver_target(gov->policy, freq, CPUFREQ_RELATION_L); |
| 191 | |
| 192 | if (!ret) { |
| 193 | gov->cur_freq = cpufreq_quick_get(gov->cpu); |
| 194 | if (freq != gov->cur_freq) |
| 195 | pr_err("cpu %d freq %u gov->cur_freq %u didn't match", |
| 196 | gov->cpu, freq, gov->cur_freq); |
| 197 | } |
| 198 | ret = gov->cur_freq; |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 199 | |
| 200 | return ret; |
| 201 | } |
| 202 | |
| 203 | static unsigned int msm_dcvs_freq_get(struct msm_dcvs_freq *self) |
| 204 | { |
| 205 | struct msm_gov *gov = |
| 206 | container_of(self, struct msm_gov, gov_notifier); |
| 207 | |
Abhijeet Dharmapurikar | 83347e0 | 2012-08-26 20:33:43 -0700 | [diff] [blame] | 208 | /* |
| 209 | * the rw_sem in cpufreq is always held when this is called. |
| 210 | * The policy->cur won't be updated in this case - so it is safe to |
| 211 | * access policy->cur |
| 212 | */ |
Abhijeet Dharmapurikar | c43f0db | 2012-08-31 20:42:53 -0700 | [diff] [blame] | 213 | return gov->policy->cur; |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 214 | } |
| 215 | |
| 216 | static int cpufreq_governor_msm(struct cpufreq_policy *policy, |
| 217 | unsigned int event) |
| 218 | { |
| 219 | unsigned int cpu = policy->cpu; |
| 220 | int ret = 0; |
| 221 | int handle = 0; |
| 222 | struct msm_gov *gov = &per_cpu(msm_gov_info, policy->cpu); |
| 223 | struct msm_dcvs_freq *dcvs_notifier = |
| 224 | &(per_cpu(msm_gov_info, cpu).gov_notifier); |
| 225 | |
| 226 | switch (event) { |
| 227 | case CPUFREQ_GOV_START: |
| 228 | if (!cpu_online(cpu)) |
| 229 | return -EINVAL; |
| 230 | BUG_ON(!policy->cur); |
| 231 | mutex_lock(&per_cpu(gov_mutex, cpu)); |
| 232 | per_cpu(msm_gov_info, cpu).cpu = cpu; |
| 233 | gov->policy = policy; |
| 234 | dcvs_notifier->core_name = core_name[cpu]; |
Abhijeet Dharmapurikar | 50bcc83 | 2012-08-31 22:10:41 -0700 | [diff] [blame] | 235 | handle = msm_dcvs_freq_sink_start(dcvs_notifier); |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 236 | BUG_ON(handle < 0); |
| 237 | msm_gov_check_limits(policy); |
| 238 | mutex_unlock(&per_cpu(gov_mutex, cpu)); |
| 239 | break; |
| 240 | |
| 241 | case CPUFREQ_GOV_STOP: |
Abhijeet Dharmapurikar | 50bcc83 | 2012-08-31 22:10:41 -0700 | [diff] [blame] | 242 | msm_dcvs_freq_sink_stop(dcvs_notifier); |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 243 | break; |
| 244 | |
| 245 | case CPUFREQ_GOV_LIMITS: |
| 246 | mutex_lock(&per_cpu(gov_mutex, cpu)); |
| 247 | msm_gov_check_limits(policy); |
| 248 | mutex_unlock(&per_cpu(gov_mutex, cpu)); |
| 249 | break; |
| 250 | }; |
| 251 | |
| 252 | return ret; |
| 253 | } |
| 254 | |
| 255 | struct cpufreq_governor cpufreq_gov_msm = { |
| 256 | .name = "msm-dcvs", |
| 257 | .governor = cpufreq_governor_msm, |
| 258 | .owner = THIS_MODULE, |
| 259 | }; |
| 260 | |
| 261 | static int __devinit msm_gov_probe(struct platform_device *pdev) |
| 262 | { |
| 263 | int ret = 0; |
| 264 | int cpu; |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 265 | struct msm_dcvs_core_info *core = NULL; |
Abhijeet Dharmapurikar | 6e9b34f | 2012-09-10 16:03:39 -0700 | [diff] [blame^] | 266 | struct msm_dcvs_core_info *core_info = NULL; |
| 267 | struct msm_gov_platform_data *pdata = pdev->dev.platform_data; |
Abhijeet Dharmapurikar | b6c0577 | 2012-08-26 18:27:53 -0700 | [diff] [blame] | 268 | int sensor = 0; |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 269 | |
| 270 | core = pdev->dev.platform_data; |
Abhijeet Dharmapurikar | 6e9b34f | 2012-09-10 16:03:39 -0700 | [diff] [blame^] | 271 | core_info = pdata->info; |
| 272 | latency = pdata->latency; |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 273 | |
| 274 | for_each_possible_cpu(cpu) { |
| 275 | mutex_init(&per_cpu(gov_mutex, cpu)); |
| 276 | snprintf(core_name[cpu], 10, "cpu%d", cpu); |
Abhijeet Dharmapurikar | b6c0577 | 2012-08-26 18:27:53 -0700 | [diff] [blame] | 277 | if (cpu < core->num_cores) |
Abhijeet Dharmapurikar | 6e9b34f | 2012-09-10 16:03:39 -0700 | [diff] [blame^] | 278 | sensor = core_info->sensors[cpu]; |
| 279 | ret = msm_dcvs_register_core(core_name[cpu], core_info, |
Abhijeet Dharmapurikar | 50bcc83 | 2012-08-31 22:10:41 -0700 | [diff] [blame] | 280 | msm_dcvs_freq_set, |
| 281 | msm_dcvs_freq_get, |
Abhijeet Dharmapurikar | 6e9b34f | 2012-09-10 16:03:39 -0700 | [diff] [blame^] | 282 | msm_dcvs_idle_notifier, |
Abhijeet Dharmapurikar | 50bcc83 | 2012-08-31 22:10:41 -0700 | [diff] [blame] | 283 | sensor); |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 284 | if (ret) |
| 285 | pr_err("Unable to register core for %d\n", cpu); |
Abhijeet Dharmapurikar | 6e9b34f | 2012-09-10 16:03:39 -0700 | [diff] [blame^] | 286 | |
| 287 | msm_gov_idle_source_init(cpu); |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 288 | } |
| 289 | |
Abhijeet Dharmapurikar | 6e9b34f | 2012-09-10 16:03:39 -0700 | [diff] [blame^] | 290 | cpu_pm_register_notifier(&idle_nb); |
| 291 | |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 292 | return cpufreq_register_governor(&cpufreq_gov_msm); |
| 293 | } |
| 294 | |
| 295 | static int __devexit msm_gov_remove(struct platform_device *pdev) |
| 296 | { |
Abhijeet Dharmapurikar | 6e9b34f | 2012-09-10 16:03:39 -0700 | [diff] [blame^] | 297 | int cpu; |
| 298 | |
| 299 | for_each_possible_cpu(cpu) { |
| 300 | msm_gov_idle_source_uninit(cpu); |
| 301 | } |
Praveen Chidambaram | 5c8adf2 | 2012-02-23 18:44:37 -0700 | [diff] [blame] | 302 | platform_set_drvdata(pdev, NULL); |
| 303 | return 0; |
| 304 | } |
| 305 | |
| 306 | static struct platform_driver msm_gov_driver = { |
| 307 | .probe = msm_gov_probe, |
| 308 | .remove = __devexit_p(msm_gov_remove), |
| 309 | .driver = { |
| 310 | .name = "msm_dcvs_gov", |
| 311 | .owner = THIS_MODULE, |
| 312 | }, |
| 313 | }; |
| 314 | |
| 315 | static int __init cpufreq_gov_msm_init(void) |
| 316 | { |
| 317 | return platform_driver_register(&msm_gov_driver); |
| 318 | } |
| 319 | late_initcall(cpufreq_gov_msm_init); |