| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 1 | /* Copyright (c) 2012, Code Aurora Forum. All rights reserved. | 
|  | 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/kthread.h> | 
|  | 19 | #include <linux/kobject.h> | 
|  | 20 | #include <linux/ktime.h> | 
|  | 21 | #include <linux/hrtimer.h> | 
|  | 22 | #include <linux/slab.h> | 
|  | 23 | #include <linux/spinlock.h> | 
|  | 24 | #include <linux/stringify.h> | 
|  | 25 | #include <linux/debugfs.h> | 
|  | 26 | #include <asm/atomic.h> | 
|  | 27 | #include <asm/page.h> | 
|  | 28 | #include <mach/msm_dcvs.h> | 
|  | 29 |  | 
|  | 30 | #define CORE_HANDLE_OFFSET (0xA0) | 
|  | 31 | #define __err(f, ...) pr_err("MSM_DCVS: %s: " f, __func__, __VA_ARGS__) | 
|  | 32 | #define __info(f, ...) pr_info("MSM_DCVS: %s: " f, __func__, __VA_ARGS__) | 
|  | 33 | #define MAX_PENDING	(5) | 
|  | 34 |  | 
|  | 35 | enum { | 
|  | 36 | MSM_DCVS_DEBUG_NOTIFIER    = BIT(0), | 
|  | 37 | MSM_DCVS_DEBUG_IDLE_PULSE  = BIT(1), | 
|  | 38 | MSM_DCVS_DEBUG_FREQ_CHANGE = BIT(2), | 
|  | 39 | }; | 
|  | 40 |  | 
|  | 41 | struct core_attribs { | 
|  | 42 | struct kobj_attribute idle_enabled; | 
|  | 43 | struct kobj_attribute freq_change_enabled; | 
|  | 44 | struct kobj_attribute actual_freq; | 
|  | 45 | struct kobj_attribute freq_change_us; | 
|  | 46 |  | 
|  | 47 | struct kobj_attribute max_time_us; | 
|  | 48 |  | 
|  | 49 | struct kobj_attribute slack_time_us; | 
|  | 50 | struct kobj_attribute scale_slack_time; | 
|  | 51 | struct kobj_attribute scale_slack_time_pct; | 
|  | 52 | struct kobj_attribute disable_pc_threshold; | 
|  | 53 | struct kobj_attribute em_window_size; | 
|  | 54 | struct kobj_attribute em_max_util_pct; | 
|  | 55 | struct kobj_attribute ss_window_size; | 
|  | 56 | struct kobj_attribute ss_util_pct; | 
|  | 57 | struct kobj_attribute ss_iobusy_conv; | 
|  | 58 |  | 
|  | 59 | struct attribute_group attrib_group; | 
|  | 60 | }; | 
|  | 61 |  | 
|  | 62 | struct dcvs_core { | 
|  | 63 | char core_name[CORE_NAME_MAX]; | 
|  | 64 | uint32_t new_freq[MAX_PENDING]; | 
|  | 65 | uint32_t actual_freq; | 
|  | 66 | uint32_t freq_change_us; | 
|  | 67 |  | 
|  | 68 | uint32_t max_time_us; /* core param */ | 
|  | 69 |  | 
|  | 70 | struct msm_dcvs_algo_param algo_param; | 
|  | 71 | struct msm_dcvs_idle *idle_driver; | 
|  | 72 | struct msm_dcvs_freq *freq_driver; | 
|  | 73 |  | 
|  | 74 | /* private */ | 
|  | 75 | int64_t time_start; | 
|  | 76 | struct mutex lock; | 
|  | 77 | spinlock_t cpu_lock; | 
|  | 78 | struct task_struct *task; | 
|  | 79 | struct core_attribs attrib; | 
|  | 80 | uint32_t handle; | 
|  | 81 | uint32_t group_id; | 
|  | 82 | uint32_t freq_pending; | 
|  | 83 | struct hrtimer timer; | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 84 | int32_t timer_disabled; | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 85 | /* track if kthread for change_freq is active */ | 
|  | 86 | int32_t change_freq_activated; | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 87 | }; | 
|  | 88 |  | 
|  | 89 | static int msm_dcvs_debug; | 
|  | 90 | static int msm_dcvs_enabled = 1; | 
|  | 91 | module_param_named(enable, msm_dcvs_enabled, int, S_IRUGO | S_IWUSR | S_IWGRP); | 
|  | 92 |  | 
|  | 93 | static struct dentry *debugfs_base; | 
|  | 94 |  | 
|  | 95 | static struct dcvs_core core_list[CORES_MAX]; | 
|  | 96 | static DEFINE_MUTEX(core_list_lock); | 
|  | 97 |  | 
|  | 98 | static struct kobject *cores_kobj; | 
|  | 99 | static struct dcvs_core *core_handles[CORES_MAX]; | 
|  | 100 |  | 
|  | 101 | /* Change core frequency, called with core mutex locked */ | 
|  | 102 | static int __msm_dcvs_change_freq(struct dcvs_core *core) | 
|  | 103 | { | 
|  | 104 | int ret = 0; | 
|  | 105 | unsigned long flags = 0; | 
|  | 106 | unsigned int requested_freq = 0; | 
|  | 107 | unsigned int prev_freq = 0; | 
|  | 108 | int64_t time_start = 0; | 
|  | 109 | int64_t time_end = 0; | 
|  | 110 | uint32_t slack_us = 0; | 
|  | 111 | uint32_t ret1 = 0; | 
|  | 112 |  | 
|  | 113 | if (!core->freq_driver || !core->freq_driver->set_frequency) { | 
|  | 114 | /* Core may have unregistered or hotplugged */ | 
|  | 115 | return -ENODEV; | 
|  | 116 | } | 
|  | 117 | repeat: | 
|  | 118 | spin_lock_irqsave(&core->cpu_lock, flags); | 
|  | 119 | if (unlikely(!core->freq_pending)) { | 
|  | 120 | spin_unlock_irqrestore(&core->cpu_lock, flags); | 
|  | 121 | return ret; | 
|  | 122 | } | 
|  | 123 | requested_freq = core->new_freq[core->freq_pending - 1]; | 
|  | 124 | if (unlikely(core->freq_pending > 1) && | 
|  | 125 | (msm_dcvs_debug & MSM_DCVS_DEBUG_FREQ_CHANGE)) { | 
|  | 126 | int i; | 
|  | 127 | for (i = 0; i < core->freq_pending - 1; i++) { | 
|  | 128 | __info("Core %s missing freq %u\n", | 
|  | 129 | core->core_name, core->new_freq[i]); | 
|  | 130 | } | 
|  | 131 | } | 
|  | 132 | time_start = core->time_start; | 
|  | 133 | core->time_start = 0; | 
|  | 134 | core->freq_pending = 0; | 
|  | 135 | /** | 
|  | 136 | * Cancel the timers, we dont want the timer firing as we are | 
|  | 137 | * changing the clock rate. Dont let idle_exit and others setup | 
|  | 138 | * timers as well. | 
|  | 139 | */ | 
|  | 140 | hrtimer_cancel(&core->timer); | 
|  | 141 | core->timer_disabled = 1; | 
|  | 142 | spin_unlock_irqrestore(&core->cpu_lock, flags); | 
|  | 143 |  | 
|  | 144 | if (requested_freq == core->actual_freq) | 
|  | 145 | return ret; | 
|  | 146 |  | 
|  | 147 | /** | 
|  | 148 | * Call the frequency sink driver to change the frequency | 
|  | 149 | * We will need to get back the actual frequency in KHz and | 
|  | 150 | * the record the time taken to change it. | 
|  | 151 | */ | 
|  | 152 | ret = core->freq_driver->set_frequency(core->freq_driver, | 
|  | 153 | requested_freq); | 
|  | 154 | if (ret <= 0) { | 
|  | 155 | __err("Core %s failed to set freq %u\n", | 
|  | 156 | core->core_name, requested_freq); | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 157 | /* continue to call TZ to get updated slack timer */ | 
|  | 158 | } else { | 
|  | 159 | prev_freq = core->actual_freq; | 
|  | 160 | core->actual_freq = ret; | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 161 | } | 
|  | 162 |  | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 163 | time_end = ktime_to_ns(ktime_get()); | 
|  | 164 | if (msm_dcvs_debug & MSM_DCVS_DEBUG_FREQ_CHANGE) | 
|  | 165 | __info("Core %s Time end %llu Time start: %llu\n", | 
|  | 166 | core->core_name, time_end, time_start); | 
|  | 167 | time_end -= time_start; | 
|  | 168 | do_div(time_end, NSEC_PER_USEC); | 
|  | 169 | core->freq_change_us = (uint32_t)time_end; | 
|  | 170 |  | 
|  | 171 | /** | 
|  | 172 | * Disable low power modes if the actual frequency is > | 
|  | 173 | * disable_pc_threshold. | 
|  | 174 | */ | 
|  | 175 | if (core->actual_freq > | 
|  | 176 | core->algo_param.disable_pc_threshold) { | 
|  | 177 | core->idle_driver->enable(core->idle_driver, | 
|  | 178 | MSM_DCVS_DISABLE_HIGH_LATENCY_MODES); | 
|  | 179 | if (msm_dcvs_debug & MSM_DCVS_DEBUG_IDLE_PULSE) | 
|  | 180 | __info("Disabling LPM for %s\n", core->core_name); | 
|  | 181 | } else if (core->actual_freq <= | 
|  | 182 | core->algo_param.disable_pc_threshold) { | 
|  | 183 | core->idle_driver->enable(core->idle_driver, | 
|  | 184 | MSM_DCVS_ENABLE_HIGH_LATENCY_MODES); | 
|  | 185 | if (msm_dcvs_debug & MSM_DCVS_DEBUG_IDLE_PULSE) | 
|  | 186 | __info("Enabling LPM for %s\n", core->core_name); | 
|  | 187 | } | 
|  | 188 |  | 
|  | 189 | /** | 
|  | 190 | * Update algorithm with new freq and time taken to change | 
|  | 191 | * to this frequency and that will get us the new slack | 
|  | 192 | * timer | 
|  | 193 | */ | 
|  | 194 | ret = msm_dcvs_scm_event(core->handle, MSM_DCVS_SCM_CLOCK_FREQ_UPDATE, | 
|  | 195 | core->actual_freq, (uint32_t)time_end, &slack_us, &ret1); | 
|  | 196 | if (!ret) { | 
|  | 197 | /* Reset the slack timer */ | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 198 | if (slack_us) { | 
|  | 199 | core->timer_disabled = 0; | 
|  | 200 | ret = hrtimer_start(&core->timer, | 
|  | 201 | ktime_set(0, slack_us * 1000), | 
|  | 202 | HRTIMER_MODE_REL_PINNED); | 
|  | 203 | if (ret) | 
|  | 204 | __err("Failed to register timer for core %s\n", | 
|  | 205 | core->core_name); | 
|  | 206 | } | 
|  | 207 | } else { | 
|  | 208 | __err("Error sending core (%s) freq change (%u)\n", | 
|  | 209 | core->core_name, core->actual_freq); | 
|  | 210 | } | 
|  | 211 |  | 
|  | 212 | if (msm_dcvs_debug & MSM_DCVS_DEBUG_FREQ_CHANGE) | 
|  | 213 | __info("Freq %u requested for core %s (actual %u prev %u) " | 
|  | 214 | "change time %u us slack time %u us\n", | 
|  | 215 | requested_freq, core->core_name, | 
|  | 216 | core->actual_freq, prev_freq, | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 217 | core->freq_change_us, slack_us); | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 218 |  | 
|  | 219 | /** | 
|  | 220 | * By the time we are done with freq changes, we could be asked to | 
|  | 221 | * change again. Check before exiting. | 
|  | 222 | */ | 
|  | 223 | if (core->freq_pending) | 
|  | 224 | goto repeat; | 
|  | 225 |  | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 226 | core->change_freq_activated = 0; | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 227 | return ret; | 
|  | 228 | } | 
|  | 229 |  | 
|  | 230 | static int msm_dcvs_do_freq(void *data) | 
|  | 231 | { | 
|  | 232 | struct dcvs_core *core = (struct dcvs_core *)data; | 
|  | 233 | static struct sched_param param = {.sched_priority = MAX_RT_PRIO - 1}; | 
|  | 234 |  | 
|  | 235 | sched_setscheduler(current, SCHED_FIFO, ¶m); | 
|  | 236 | set_current_state(TASK_UNINTERRUPTIBLE); | 
|  | 237 |  | 
|  | 238 | while (!kthread_should_stop()) { | 
|  | 239 | mutex_lock(&core->lock); | 
|  | 240 | __msm_dcvs_change_freq(core); | 
|  | 241 | mutex_unlock(&core->lock); | 
|  | 242 |  | 
|  | 243 | schedule(); | 
|  | 244 |  | 
|  | 245 | if (kthread_should_stop()) | 
|  | 246 | break; | 
|  | 247 |  | 
|  | 248 | set_current_state(TASK_UNINTERRUPTIBLE); | 
|  | 249 | } | 
|  | 250 |  | 
|  | 251 | __set_current_state(TASK_RUNNING); | 
|  | 252 |  | 
|  | 253 | return 0; | 
|  | 254 | } | 
|  | 255 |  | 
|  | 256 | static int msm_dcvs_update_freq(struct dcvs_core *core, | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 257 | enum msm_dcvs_scm_event event, uint32_t param0, | 
|  | 258 | uint32_t *ret1, int *freq_changed) | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 259 | { | 
|  | 260 | int ret = 0; | 
|  | 261 | unsigned long flags = 0; | 
|  | 262 | uint32_t new_freq = 0; | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 263 |  | 
|  | 264 | spin_lock_irqsave(&core->cpu_lock, flags); | 
|  | 265 | ret = msm_dcvs_scm_event(core->handle, event, param0, | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 266 | core->actual_freq, &new_freq, ret1); | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 267 | if (ret) { | 
|  | 268 | __err("Error (%d) sending SCM event %d for core %s\n", | 
|  | 269 | ret, event, core->core_name); | 
|  | 270 | goto freq_done; | 
|  | 271 | } | 
|  | 272 |  | 
|  | 273 | if ((core->actual_freq != new_freq) && | 
|  | 274 | (core->new_freq[core->freq_pending] != new_freq)) { | 
|  | 275 | if (core->freq_pending >= MAX_PENDING - 1) | 
|  | 276 | core->freq_pending = MAX_PENDING - 1; | 
|  | 277 | core->new_freq[core->freq_pending++] = new_freq; | 
|  | 278 | core->time_start = ktime_to_ns(ktime_get()); | 
|  | 279 |  | 
|  | 280 | /* Schedule the frequency change */ | 
|  | 281 | if (!core->task) | 
|  | 282 | __err("Uninitialized task for core %s\n", | 
|  | 283 | core->core_name); | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 284 | else { | 
|  | 285 | if (freq_changed) | 
|  | 286 | *freq_changed = 1; | 
|  | 287 | core->change_freq_activated = 1; | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 288 | wake_up_process(core->task); | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 289 | } | 
|  | 290 | } else { | 
|  | 291 | if (freq_changed) | 
|  | 292 | *freq_changed = 0; | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 293 | } | 
|  | 294 | freq_done: | 
|  | 295 | spin_unlock_irqrestore(&core->cpu_lock, flags); | 
|  | 296 |  | 
|  | 297 | return ret; | 
|  | 298 | } | 
|  | 299 |  | 
|  | 300 | static enum hrtimer_restart msm_dcvs_core_slack_timer(struct hrtimer *timer) | 
|  | 301 | { | 
|  | 302 | int ret = 0; | 
|  | 303 | struct dcvs_core *core = container_of(timer, struct dcvs_core, timer); | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 304 | uint32_t ret1; | 
|  | 305 | uint32_t ret2; | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 306 |  | 
|  | 307 | if (msm_dcvs_debug & MSM_DCVS_DEBUG_FREQ_CHANGE) | 
|  | 308 | __info("Slack timer fired for core %s\n", core->core_name); | 
|  | 309 |  | 
|  | 310 | /** | 
|  | 311 | * Timer expired, notify TZ | 
|  | 312 | * Dont care about the third arg. | 
|  | 313 | */ | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 314 | ret = msm_dcvs_update_freq(core, MSM_DCVS_SCM_QOS_TIMER_EXPIRED, 0, | 
|  | 315 | &ret1, &ret2); | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 316 | if (ret) | 
|  | 317 | __err("Timer expired for core %s but failed to notify.\n", | 
|  | 318 | core->core_name); | 
|  | 319 |  | 
|  | 320 | return HRTIMER_NORESTART; | 
|  | 321 | } | 
|  | 322 |  | 
|  | 323 | /* Helper functions and macros for sysfs nodes for a core */ | 
|  | 324 | #define CORE_FROM_ATTRIBS(attr, name) \ | 
|  | 325 | container_of(container_of(attr, struct core_attribs, name), \ | 
|  | 326 | struct dcvs_core, attrib); | 
|  | 327 |  | 
|  | 328 | #define DCVS_PARAM_SHOW(_name, v) \ | 
|  | 329 | static ssize_t msm_dcvs_attr_##_name##_show(struct kobject *kobj, \ | 
|  | 330 | struct kobj_attribute *attr, char *buf) \ | 
|  | 331 | { \ | 
|  | 332 | struct dcvs_core *core = CORE_FROM_ATTRIBS(attr, _name); \ | 
|  | 333 | return snprintf(buf, PAGE_SIZE, "%d\n", v); \ | 
|  | 334 | } | 
|  | 335 |  | 
|  | 336 | #define DCVS_ALGO_PARAM(_name) \ | 
|  | 337 | static ssize_t msm_dcvs_attr_##_name##_show(struct kobject *kobj,\ | 
|  | 338 | struct kobj_attribute *attr, char *buf) \ | 
|  | 339 | { \ | 
|  | 340 | struct dcvs_core *core = CORE_FROM_ATTRIBS(attr, _name); \ | 
|  | 341 | return snprintf(buf, PAGE_SIZE, "%d\n", core->algo_param._name); \ | 
|  | 342 | } \ | 
|  | 343 | static ssize_t msm_dcvs_attr_##_name##_store(struct kobject *kobj, \ | 
|  | 344 | struct kobj_attribute *attr, const char *buf, size_t count) \ | 
|  | 345 | { \ | 
|  | 346 | int ret = 0; \ | 
|  | 347 | uint32_t val = 0; \ | 
|  | 348 | struct dcvs_core *core = CORE_FROM_ATTRIBS(attr, _name); \ | 
|  | 349 | mutex_lock(&core->lock); \ | 
|  | 350 | ret = kstrtouint(buf, 10, &val); \ | 
|  | 351 | if (ret) { \ | 
|  | 352 | __err("Invalid input %s for %s\n", buf, __stringify(_name));\ | 
|  | 353 | } else { \ | 
|  | 354 | uint32_t old_val = core->algo_param._name; \ | 
|  | 355 | core->algo_param._name = val; \ | 
|  | 356 | ret = msm_dcvs_scm_set_algo_params(core->handle, \ | 
|  | 357 | &core->algo_param); \ | 
|  | 358 | if (ret) { \ | 
|  | 359 | core->algo_param._name = old_val; \ | 
|  | 360 | __err("Error(%d) in setting %d for algo param %s\n",\ | 
|  | 361 | ret, val, __stringify(_name)); \ | 
|  | 362 | } \ | 
|  | 363 | } \ | 
|  | 364 | mutex_unlock(&core->lock); \ | 
|  | 365 | return count; \ | 
|  | 366 | } | 
|  | 367 |  | 
|  | 368 | #define DCVS_RO_ATTRIB(i, _name) \ | 
|  | 369 | core->attrib._name.attr.name = __stringify(_name); \ | 
|  | 370 | core->attrib._name.attr.mode = S_IRUGO; \ | 
|  | 371 | core->attrib._name.show = msm_dcvs_attr_##_name##_show; \ | 
|  | 372 | core->attrib._name.store = NULL; \ | 
|  | 373 | core->attrib.attrib_group.attrs[i] = &core->attrib._name.attr; | 
|  | 374 |  | 
|  | 375 | #define DCVS_RW_ATTRIB(i, _name) \ | 
|  | 376 | core->attrib._name.attr.name = __stringify(_name); \ | 
|  | 377 | core->attrib._name.attr.mode = S_IRUGO | S_IWUSR; \ | 
|  | 378 | core->attrib._name.show = msm_dcvs_attr_##_name##_show; \ | 
|  | 379 | core->attrib._name.store = msm_dcvs_attr_##_name##_store; \ | 
|  | 380 | core->attrib.attrib_group.attrs[i] = &core->attrib._name.attr; | 
|  | 381 |  | 
|  | 382 | /** | 
|  | 383 | * Function declarations for different attributes. | 
|  | 384 | * Gets used when setting the attribute show and store parameters. | 
|  | 385 | */ | 
|  | 386 | DCVS_PARAM_SHOW(idle_enabled, (core->idle_driver != NULL)) | 
|  | 387 | DCVS_PARAM_SHOW(freq_change_enabled, (core->freq_driver != NULL)) | 
|  | 388 | DCVS_PARAM_SHOW(actual_freq, (core->actual_freq)) | 
|  | 389 | DCVS_PARAM_SHOW(freq_change_us, (core->freq_change_us)) | 
|  | 390 | DCVS_PARAM_SHOW(max_time_us, (core->max_time_us)) | 
|  | 391 |  | 
|  | 392 | DCVS_ALGO_PARAM(slack_time_us) | 
|  | 393 | DCVS_ALGO_PARAM(scale_slack_time) | 
|  | 394 | DCVS_ALGO_PARAM(scale_slack_time_pct) | 
|  | 395 | DCVS_ALGO_PARAM(disable_pc_threshold) | 
|  | 396 | DCVS_ALGO_PARAM(em_window_size) | 
|  | 397 | DCVS_ALGO_PARAM(em_max_util_pct) | 
|  | 398 | DCVS_ALGO_PARAM(ss_window_size) | 
|  | 399 | DCVS_ALGO_PARAM(ss_util_pct) | 
|  | 400 | DCVS_ALGO_PARAM(ss_iobusy_conv) | 
|  | 401 |  | 
|  | 402 | static int msm_dcvs_setup_core_sysfs(struct dcvs_core *core) | 
|  | 403 | { | 
|  | 404 | int ret = 0; | 
|  | 405 | struct kobject *core_kobj = NULL; | 
|  | 406 | const int attr_count = 15; | 
|  | 407 |  | 
|  | 408 | BUG_ON(!cores_kobj); | 
|  | 409 |  | 
|  | 410 | core->attrib.attrib_group.attrs = | 
|  | 411 | kzalloc(attr_count * sizeof(struct attribute *), GFP_KERNEL); | 
|  | 412 |  | 
|  | 413 | if (!core->attrib.attrib_group.attrs) { | 
|  | 414 | ret = -ENOMEM; | 
|  | 415 | goto done; | 
|  | 416 | } | 
|  | 417 |  | 
|  | 418 | DCVS_RO_ATTRIB(0, idle_enabled); | 
|  | 419 | DCVS_RO_ATTRIB(1, freq_change_enabled); | 
|  | 420 | DCVS_RO_ATTRIB(2, actual_freq); | 
|  | 421 | DCVS_RO_ATTRIB(3, freq_change_us); | 
|  | 422 | DCVS_RO_ATTRIB(4, max_time_us); | 
|  | 423 |  | 
|  | 424 | DCVS_RW_ATTRIB(5, slack_time_us); | 
|  | 425 | DCVS_RW_ATTRIB(6, scale_slack_time); | 
|  | 426 | DCVS_RW_ATTRIB(7, scale_slack_time_pct); | 
|  | 427 | DCVS_RW_ATTRIB(8, disable_pc_threshold); | 
|  | 428 | DCVS_RW_ATTRIB(9, em_window_size); | 
|  | 429 | DCVS_RW_ATTRIB(10, em_max_util_pct); | 
|  | 430 | DCVS_RW_ATTRIB(11, ss_window_size); | 
|  | 431 | DCVS_RW_ATTRIB(12, ss_util_pct); | 
|  | 432 | DCVS_RW_ATTRIB(13, ss_iobusy_conv); | 
|  | 433 |  | 
|  | 434 | core->attrib.attrib_group.attrs[14] = NULL; | 
|  | 435 |  | 
|  | 436 | core_kobj = kobject_create_and_add(core->core_name, cores_kobj); | 
|  | 437 | if (!core_kobj) { | 
|  | 438 | ret = -ENOMEM; | 
|  | 439 | goto done; | 
|  | 440 | } | 
|  | 441 |  | 
|  | 442 | ret = sysfs_create_group(core_kobj, &core->attrib.attrib_group); | 
|  | 443 | if (ret) | 
|  | 444 | __err("Cannot create core %s attr group\n", core->core_name); | 
|  | 445 | else if (msm_dcvs_debug & MSM_DCVS_DEBUG_NOTIFIER) | 
|  | 446 | __info("Setting up attributes for core %s\n", core->core_name); | 
|  | 447 |  | 
|  | 448 | done: | 
|  | 449 | if (ret) { | 
|  | 450 | kfree(core->attrib.attrib_group.attrs); | 
|  | 451 | kobject_del(core_kobj); | 
|  | 452 | } | 
|  | 453 |  | 
|  | 454 | return ret; | 
|  | 455 | } | 
|  | 456 |  | 
|  | 457 | /* Return the core if found or add to list if @add_to_list is true */ | 
|  | 458 | static struct dcvs_core *msm_dcvs_get_core(const char *name, int add_to_list) | 
|  | 459 | { | 
|  | 460 | struct dcvs_core *core = NULL; | 
|  | 461 | int i; | 
|  | 462 | int empty = -1; | 
|  | 463 |  | 
|  | 464 | if (!name[0] || | 
|  | 465 | (strnlen(name, CORE_NAME_MAX - 1) == CORE_NAME_MAX - 1)) | 
|  | 466 | return core; | 
|  | 467 |  | 
|  | 468 | mutex_lock(&core_list_lock); | 
|  | 469 | for (i = 0; i < CORES_MAX; i++) { | 
|  | 470 | core = &core_list[i]; | 
|  | 471 | if ((empty < 0) && !core->core_name[0]) { | 
|  | 472 | empty = i; | 
|  | 473 | continue; | 
|  | 474 | } | 
|  | 475 | if (!strncmp(name, core->core_name, CORE_NAME_MAX)) | 
|  | 476 | break; | 
|  | 477 | } | 
|  | 478 |  | 
|  | 479 | /* Check for core_list full */ | 
|  | 480 | if ((i == CORES_MAX) && (empty < 0)) { | 
|  | 481 | mutex_unlock(&core_list_lock); | 
|  | 482 | return NULL; | 
|  | 483 | } | 
|  | 484 |  | 
|  | 485 | if (i == CORES_MAX && add_to_list) { | 
|  | 486 | core = &core_list[empty]; | 
|  | 487 | strlcpy(core->core_name, name, CORE_NAME_MAX); | 
|  | 488 | mutex_init(&core->lock); | 
|  | 489 | spin_lock_init(&core->cpu_lock); | 
|  | 490 | core->handle = empty + CORE_HANDLE_OFFSET; | 
|  | 491 | hrtimer_init(&core->timer, | 
|  | 492 | CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED); | 
|  | 493 | core->timer.function = msm_dcvs_core_slack_timer; | 
|  | 494 | } | 
|  | 495 | mutex_unlock(&core_list_lock); | 
|  | 496 |  | 
|  | 497 | return core; | 
|  | 498 | } | 
|  | 499 |  | 
|  | 500 | int msm_dcvs_register_core(const char *core_name, uint32_t group_id, | 
|  | 501 | struct msm_dcvs_core_info *info) | 
|  | 502 | { | 
|  | 503 | int ret = -EINVAL; | 
|  | 504 | struct dcvs_core *core = NULL; | 
|  | 505 |  | 
|  | 506 | if (!core_name || !core_name[0]) | 
|  | 507 | return ret; | 
|  | 508 |  | 
|  | 509 | core = msm_dcvs_get_core(core_name, true); | 
|  | 510 | if (!core) | 
|  | 511 | return ret; | 
|  | 512 |  | 
|  | 513 | mutex_lock(&core->lock); | 
|  | 514 | if (group_id) { | 
|  | 515 | /** | 
|  | 516 | * Create a group for cores, if this core is part of a group | 
|  | 517 | * if the group_id is 0, the core is not part of a group. | 
|  | 518 | * If the group_id already exits, it will through an error | 
|  | 519 | * which we will ignore. | 
|  | 520 | */ | 
|  | 521 | ret = msm_dcvs_scm_create_group(group_id); | 
|  | 522 | if (ret == -ENOMEM) | 
|  | 523 | goto bail; | 
|  | 524 | } | 
|  | 525 | core->group_id = group_id; | 
|  | 526 |  | 
|  | 527 | core->max_time_us = info->core_param.max_time_us; | 
|  | 528 | memcpy(&core->algo_param, &info->algo_param, | 
|  | 529 | sizeof(struct msm_dcvs_algo_param)); | 
|  | 530 |  | 
|  | 531 | ret = msm_dcvs_scm_register_core(core->handle, group_id, | 
|  | 532 | &info->core_param, info->freq_tbl); | 
|  | 533 | if (ret) | 
|  | 534 | goto bail; | 
|  | 535 |  | 
|  | 536 | ret = msm_dcvs_scm_set_algo_params(core->handle, &info->algo_param); | 
|  | 537 | if (ret) | 
|  | 538 | goto bail; | 
|  | 539 |  | 
|  | 540 | ret = msm_dcvs_setup_core_sysfs(core); | 
|  | 541 | if (ret) { | 
|  | 542 | __err("Unable to setup core %s sysfs\n", core->core_name); | 
|  | 543 | core_handles[core->handle - CORE_HANDLE_OFFSET] = NULL; | 
|  | 544 | goto bail; | 
|  | 545 | } | 
|  | 546 |  | 
|  | 547 | bail: | 
|  | 548 | mutex_unlock(&core->lock); | 
|  | 549 | return ret; | 
|  | 550 | } | 
|  | 551 | EXPORT_SYMBOL(msm_dcvs_register_core); | 
|  | 552 |  | 
|  | 553 | int msm_dcvs_freq_sink_register(struct msm_dcvs_freq *drv) | 
|  | 554 | { | 
|  | 555 | int ret = -EINVAL; | 
|  | 556 | struct dcvs_core *core = NULL; | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 557 | uint32_t ret1; | 
|  | 558 | uint32_t ret2; | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 559 |  | 
|  | 560 | if (!drv || !drv->core_name) | 
|  | 561 | return ret; | 
|  | 562 |  | 
|  | 563 | core = msm_dcvs_get_core(drv->core_name, true); | 
|  | 564 | if (!core) | 
|  | 565 | return ret; | 
|  | 566 |  | 
|  | 567 | mutex_lock(&core->lock); | 
|  | 568 | if (core->freq_driver && (msm_dcvs_debug & MSM_DCVS_DEBUG_NOTIFIER)) | 
|  | 569 | __info("Frequency notifier for %s being replaced\n", | 
|  | 570 | core->core_name); | 
|  | 571 | core->freq_driver = drv; | 
|  | 572 | core->task = kthread_create(msm_dcvs_do_freq, (void *)core, | 
|  | 573 | "msm_dcvs/%d", core->handle); | 
|  | 574 | if (IS_ERR(core->task)) { | 
|  | 575 | mutex_unlock(&core->lock); | 
|  | 576 | return -EFAULT; | 
|  | 577 | } | 
|  | 578 |  | 
|  | 579 | if (msm_dcvs_debug & MSM_DCVS_DEBUG_IDLE_PULSE) | 
|  | 580 | __info("Enabling idle pulse for %s\n", core->core_name); | 
|  | 581 |  | 
|  | 582 | if (core->idle_driver) { | 
|  | 583 | core->actual_freq = core->freq_driver->get_frequency(drv); | 
|  | 584 | /* Notify TZ to start receiving idle info for the core */ | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 585 | ret = msm_dcvs_update_freq(core, MSM_DCVS_SCM_ENABLE_CORE, 1, | 
|  | 586 | &ret1, &ret2); | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 587 | core->idle_driver->enable(core->idle_driver, | 
|  | 588 | MSM_DCVS_ENABLE_IDLE_PULSE); | 
|  | 589 | } | 
|  | 590 |  | 
|  | 591 | mutex_unlock(&core->lock); | 
|  | 592 |  | 
|  | 593 | return core->handle; | 
|  | 594 | } | 
|  | 595 | EXPORT_SYMBOL(msm_dcvs_freq_sink_register); | 
|  | 596 |  | 
|  | 597 | int msm_dcvs_freq_sink_unregister(struct msm_dcvs_freq *drv) | 
|  | 598 | { | 
|  | 599 | int ret = -EINVAL; | 
|  | 600 | struct dcvs_core *core = NULL; | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 601 | uint32_t ret1; | 
|  | 602 | uint32_t ret2; | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 603 |  | 
|  | 604 | if (!drv || !drv->core_name) | 
|  | 605 | return ret; | 
|  | 606 |  | 
|  | 607 | core = msm_dcvs_get_core(drv->core_name, false); | 
|  | 608 | if (!core) | 
|  | 609 | return ret; | 
|  | 610 |  | 
|  | 611 | mutex_lock(&core->lock); | 
|  | 612 | if (msm_dcvs_debug & MSM_DCVS_DEBUG_IDLE_PULSE) | 
|  | 613 | __info("Disabling idle pulse for %s\n", core->core_name); | 
|  | 614 | if (core->idle_driver) { | 
|  | 615 | core->idle_driver->enable(core->idle_driver, | 
|  | 616 | MSM_DCVS_DISABLE_IDLE_PULSE); | 
|  | 617 | /* Notify TZ to stop receiving idle info for the core */ | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 618 | ret = msm_dcvs_update_freq(core, MSM_DCVS_SCM_ENABLE_CORE, 0, | 
|  | 619 | &ret1, &ret2); | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 620 | hrtimer_cancel(&core->timer); | 
|  | 621 | core->idle_driver->enable(core->idle_driver, | 
|  | 622 | MSM_DCVS_ENABLE_HIGH_LATENCY_MODES); | 
|  | 623 | if (msm_dcvs_debug & MSM_DCVS_DEBUG_IDLE_PULSE) | 
|  | 624 | __info("Enabling LPM for %s\n", core->core_name); | 
|  | 625 | } | 
|  | 626 | core->freq_pending = 0; | 
|  | 627 | core->freq_driver = NULL; | 
|  | 628 | mutex_unlock(&core->lock); | 
|  | 629 | kthread_stop(core->task); | 
|  | 630 |  | 
|  | 631 | return 0; | 
|  | 632 | } | 
|  | 633 | EXPORT_SYMBOL(msm_dcvs_freq_sink_unregister); | 
|  | 634 |  | 
|  | 635 | int msm_dcvs_idle_source_register(struct msm_dcvs_idle *drv) | 
|  | 636 | { | 
|  | 637 | int ret = -EINVAL; | 
|  | 638 | struct dcvs_core *core = NULL; | 
|  | 639 |  | 
|  | 640 | if (!drv || !drv->core_name) | 
|  | 641 | return ret; | 
|  | 642 |  | 
|  | 643 | core = msm_dcvs_get_core(drv->core_name, true); | 
|  | 644 | if (!core) | 
|  | 645 | return ret; | 
|  | 646 |  | 
|  | 647 | mutex_lock(&core->lock); | 
|  | 648 | if (core->idle_driver && (msm_dcvs_debug & MSM_DCVS_DEBUG_NOTIFIER)) | 
|  | 649 | __info("Idle notifier for %s being replaced\n", | 
|  | 650 | core->core_name); | 
|  | 651 | core->idle_driver = drv; | 
|  | 652 | mutex_unlock(&core->lock); | 
|  | 653 |  | 
|  | 654 | return core->handle; | 
|  | 655 | } | 
|  | 656 | EXPORT_SYMBOL(msm_dcvs_idle_source_register); | 
|  | 657 |  | 
|  | 658 | int msm_dcvs_idle_source_unregister(struct msm_dcvs_idle *drv) | 
|  | 659 | { | 
|  | 660 | int ret = -EINVAL; | 
|  | 661 | struct dcvs_core *core = NULL; | 
|  | 662 |  | 
|  | 663 | if (!drv || !drv->core_name) | 
|  | 664 | return ret; | 
|  | 665 |  | 
|  | 666 | core = msm_dcvs_get_core(drv->core_name, false); | 
|  | 667 | if (!core) | 
|  | 668 | return ret; | 
|  | 669 |  | 
|  | 670 | mutex_lock(&core->lock); | 
|  | 671 | core->idle_driver = NULL; | 
|  | 672 | mutex_unlock(&core->lock); | 
|  | 673 |  | 
|  | 674 | return 0; | 
|  | 675 | } | 
|  | 676 | EXPORT_SYMBOL(msm_dcvs_idle_source_unregister); | 
|  | 677 |  | 
|  | 678 | int msm_dcvs_idle(int handle, enum msm_core_idle_state state, uint32_t iowaited) | 
|  | 679 | { | 
|  | 680 | int ret = 0; | 
|  | 681 | struct dcvs_core *core = NULL; | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 682 | uint32_t timer_interval_us = 0; | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 683 | uint32_t r0, r1; | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 684 | uint32_t freq_changed = 0; | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 685 |  | 
|  | 686 | if (handle >= CORE_HANDLE_OFFSET && | 
|  | 687 | (handle - CORE_HANDLE_OFFSET) < CORES_MAX) | 
|  | 688 | core = &core_list[handle - CORE_HANDLE_OFFSET]; | 
|  | 689 |  | 
|  | 690 | BUG_ON(!core); | 
|  | 691 |  | 
|  | 692 | if (msm_dcvs_debug & MSM_DCVS_DEBUG_IDLE_PULSE) | 
|  | 693 | __info("Core %s idle state %d\n", core->core_name, state); | 
|  | 694 |  | 
|  | 695 | switch (state) { | 
|  | 696 | case MSM_DCVS_IDLE_ENTER: | 
|  | 697 | hrtimer_cancel(&core->timer); | 
|  | 698 | ret = msm_dcvs_scm_event(core->handle, | 
|  | 699 | MSM_DCVS_SCM_IDLE_ENTER, 0, 0, &r0, &r1); | 
|  | 700 | if (ret) | 
|  | 701 | __err("Error (%d) sending idle enter for %s\n", | 
|  | 702 | ret, core->core_name); | 
|  | 703 | break; | 
|  | 704 |  | 
|  | 705 | case MSM_DCVS_IDLE_EXIT: | 
|  | 706 | hrtimer_cancel(&core->timer); | 
|  | 707 | ret = msm_dcvs_update_freq(core, MSM_DCVS_SCM_IDLE_EXIT, | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 708 | iowaited, &timer_interval_us, &freq_changed); | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 709 | if (ret) | 
|  | 710 | __err("Error (%d) sending idle exit for %s\n", | 
|  | 711 | ret, core->core_name); | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 712 | /* only start slack timer if change_freq won't */ | 
|  | 713 | if (freq_changed || core->change_freq_activated) | 
|  | 714 | break; | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 715 | if (timer_interval_us && !core->timer_disabled) { | 
|  | 716 | ret = hrtimer_start(&core->timer, | 
|  | 717 | ktime_set(0, timer_interval_us * 1000), | 
|  | 718 | HRTIMER_MODE_REL_PINNED); | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 719 |  | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 720 | if (ret) | 
|  | 721 | __err("Failed to register timer for core %s\n", | 
| Eugene Seah | 76af983 | 2012-03-28 18:43:53 -0600 | [diff] [blame] | 722 | core->core_name); | 
| Praveen Chidambaram | f53ef1b | 2011-12-06 08:27:49 -0700 | [diff] [blame] | 723 | } | 
|  | 724 | break; | 
|  | 725 | } | 
|  | 726 |  | 
|  | 727 | return ret; | 
|  | 728 | } | 
|  | 729 | EXPORT_SYMBOL(msm_dcvs_idle); | 
|  | 730 |  | 
|  | 731 | static int __init msm_dcvs_late_init(void) | 
|  | 732 | { | 
|  | 733 | struct kobject *module_kobj = NULL; | 
|  | 734 | int ret = 0; | 
|  | 735 |  | 
|  | 736 | module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME); | 
|  | 737 | if (!module_kobj) { | 
|  | 738 | pr_err("%s: cannot find kobject for module %s\n", | 
|  | 739 | __func__, KBUILD_MODNAME); | 
|  | 740 | ret = -ENOENT; | 
|  | 741 | goto err; | 
|  | 742 | } | 
|  | 743 |  | 
|  | 744 | cores_kobj = kobject_create_and_add("cores", module_kobj); | 
|  | 745 | if (!cores_kobj) { | 
|  | 746 | __err("Cannot create %s kobject\n", "cores"); | 
|  | 747 | ret = -ENOMEM; | 
|  | 748 | goto err; | 
|  | 749 | } | 
|  | 750 |  | 
|  | 751 | debugfs_base = debugfs_create_dir("msm_dcvs", NULL); | 
|  | 752 | if (!debugfs_base) { | 
|  | 753 | __err("Cannot create debugfs base %s\n", "msm_dcvs"); | 
|  | 754 | ret = -ENOENT; | 
|  | 755 | goto err; | 
|  | 756 | } | 
|  | 757 |  | 
|  | 758 | if (!debugfs_create_u32("debug_mask", S_IRUGO | S_IWUSR, | 
|  | 759 | debugfs_base, &msm_dcvs_debug)) { | 
|  | 760 | __err("Cannot create debugfs entry %s\n", "debug_mask"); | 
|  | 761 | ret = -ENOMEM; | 
|  | 762 | goto err; | 
|  | 763 | } | 
|  | 764 |  | 
|  | 765 | err: | 
|  | 766 | if (ret) { | 
|  | 767 | kobject_del(cores_kobj); | 
|  | 768 | cores_kobj = NULL; | 
|  | 769 | debugfs_remove(debugfs_base); | 
|  | 770 | } | 
|  | 771 |  | 
|  | 772 | return ret; | 
|  | 773 | } | 
|  | 774 | late_initcall(msm_dcvs_late_init); | 
|  | 775 |  | 
|  | 776 | static int __init msm_dcvs_early_init(void) | 
|  | 777 | { | 
|  | 778 | int ret = 0; | 
|  | 779 |  | 
|  | 780 | if (!msm_dcvs_enabled) { | 
|  | 781 | __info("Not enabled (%d)\n", msm_dcvs_enabled); | 
|  | 782 | return 0; | 
|  | 783 | } | 
|  | 784 |  | 
|  | 785 | ret = msm_dcvs_scm_init(10 * 1024); | 
|  | 786 | if (ret) | 
|  | 787 | __err("Unable to initialize DCVS err=%d\n", ret); | 
|  | 788 |  | 
|  | 789 | return ret; | 
|  | 790 | } | 
|  | 791 | postcore_initcall(msm_dcvs_early_init); |