Harshitha Sai Neelati | 75ce3b1b | 2019-09-04 17:32:28 +0530 | [diff] [blame] | 1 | /* Copyright (c) 2010-2017, 2019 The Linux Foundation. All rights reserved. |
Oleg Perelet | b906a19 | 2017-01-04 09:50:02 -0800 | [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 | #include <linux/errno.h> |
| 14 | #include <linux/module.h> |
| 15 | #include <linux/devfreq.h> |
| 16 | #include <linux/math64.h> |
| 17 | #include <linux/spinlock.h> |
| 18 | #include <linux/slab.h> |
| 19 | #include <linux/io.h> |
| 20 | #include <linux/ftrace.h> |
| 21 | #include <linux/mm.h> |
| 22 | #include <linux/msm_adreno_devfreq.h> |
| 23 | #include <asm/cacheflush.h> |
| 24 | #include <soc/qcom/scm.h> |
| 25 | #include "governor.h" |
| 26 | |
| 27 | static DEFINE_SPINLOCK(tz_lock); |
| 28 | static DEFINE_SPINLOCK(sample_lock); |
| 29 | static DEFINE_SPINLOCK(suspend_lock); |
| 30 | /* |
| 31 | * FLOOR is 5msec to capture up to 3 re-draws |
| 32 | * per frame for 60fps content. |
| 33 | */ |
| 34 | #define FLOOR 5000 |
| 35 | /* |
| 36 | * MIN_BUSY is 1 msec for the sample to be sent |
| 37 | */ |
| 38 | #define MIN_BUSY 1000 |
| 39 | #define MAX_TZ_VERSION 0 |
| 40 | |
| 41 | /* |
| 42 | * CEILING is 50msec, larger than any standard |
| 43 | * frame length, but less than the idle timer. |
| 44 | */ |
| 45 | #define CEILING 50000 |
| 46 | #define TZ_RESET_ID 0x3 |
| 47 | #define TZ_UPDATE_ID 0x4 |
| 48 | #define TZ_INIT_ID 0x6 |
| 49 | |
| 50 | #define TZ_RESET_ID_64 0x7 |
| 51 | #define TZ_UPDATE_ID_64 0x8 |
| 52 | #define TZ_INIT_ID_64 0x9 |
| 53 | |
| 54 | #define TZ_V2_UPDATE_ID_64 0xA |
| 55 | #define TZ_V2_INIT_ID_64 0xB |
| 56 | #define TZ_V2_INIT_CA_ID_64 0xC |
| 57 | #define TZ_V2_UPDATE_WITH_CA_ID_64 0xD |
| 58 | |
| 59 | #define TAG "msm_adreno_tz: " |
| 60 | |
| 61 | static u64 suspend_time; |
| 62 | static u64 suspend_start; |
| 63 | static unsigned long acc_total, acc_relative_busy; |
| 64 | |
| 65 | static struct msm_adreno_extended_profile *partner_gpu_profile; |
| 66 | static void do_partner_start_event(struct work_struct *work); |
| 67 | static void do_partner_stop_event(struct work_struct *work); |
| 68 | static void do_partner_suspend_event(struct work_struct *work); |
| 69 | static void do_partner_resume_event(struct work_struct *work); |
| 70 | |
| 71 | static struct workqueue_struct *workqueue; |
| 72 | |
| 73 | /* |
| 74 | * Returns GPU suspend time in millisecond. |
| 75 | */ |
| 76 | u64 suspend_time_ms(void) |
| 77 | { |
| 78 | u64 suspend_sampling_time; |
| 79 | u64 time_diff = 0; |
| 80 | |
| 81 | if (suspend_start == 0) |
| 82 | return 0; |
| 83 | |
| 84 | suspend_sampling_time = (u64)ktime_to_ms(ktime_get()); |
| 85 | time_diff = suspend_sampling_time - suspend_start; |
| 86 | /* Update the suspend_start sample again */ |
| 87 | suspend_start = suspend_sampling_time; |
| 88 | return time_diff; |
| 89 | } |
| 90 | |
| 91 | static ssize_t gpu_load_show(struct device *dev, |
| 92 | struct device_attribute *attr, |
| 93 | char *buf) |
| 94 | { |
| 95 | unsigned long sysfs_busy_perc = 0; |
| 96 | /* |
| 97 | * Average out the samples taken since last read |
| 98 | * This will keep the average value in sync with |
| 99 | * with the client sampling duration. |
| 100 | */ |
| 101 | spin_lock(&sample_lock); |
| 102 | if (acc_total) |
| 103 | sysfs_busy_perc = (acc_relative_busy * 100) / acc_total; |
| 104 | |
| 105 | /* Reset the parameters */ |
| 106 | acc_total = 0; |
| 107 | acc_relative_busy = 0; |
| 108 | spin_unlock(&sample_lock); |
| 109 | return snprintf(buf, PAGE_SIZE, "%lu\n", sysfs_busy_perc); |
| 110 | } |
| 111 | |
| 112 | /* |
| 113 | * Returns the time in ms for which gpu was in suspend state |
| 114 | * since last time the entry is read. |
| 115 | */ |
| 116 | static ssize_t suspend_time_show(struct device *dev, |
| 117 | struct device_attribute *attr, |
| 118 | char *buf) |
| 119 | { |
| 120 | u64 time_diff = 0; |
| 121 | |
| 122 | spin_lock(&suspend_lock); |
| 123 | time_diff = suspend_time_ms(); |
| 124 | /* |
| 125 | * Adding the previous suspend time also as the gpu |
| 126 | * can go and come out of suspend states in between |
| 127 | * reads also and we should have the total suspend |
| 128 | * since last read. |
| 129 | */ |
| 130 | time_diff += suspend_time; |
| 131 | suspend_time = 0; |
| 132 | spin_unlock(&suspend_lock); |
| 133 | |
| 134 | return snprintf(buf, PAGE_SIZE, "%llu\n", time_diff); |
| 135 | } |
| 136 | |
| 137 | static DEVICE_ATTR(gpu_load, 0444, gpu_load_show, NULL); |
| 138 | |
| 139 | static DEVICE_ATTR(suspend_time, 0444, |
| 140 | suspend_time_show, |
| 141 | NULL); |
| 142 | |
| 143 | static const struct device_attribute *adreno_tz_attr_list[] = { |
| 144 | &dev_attr_gpu_load, |
| 145 | &dev_attr_suspend_time, |
| 146 | NULL |
| 147 | }; |
| 148 | |
| 149 | void compute_work_load(struct devfreq_dev_status *stats, |
| 150 | struct devfreq_msm_adreno_tz_data *priv, |
| 151 | struct devfreq *devfreq) |
| 152 | { |
Harshitha Sai Neelati | 75ce3b1b | 2019-09-04 17:32:28 +0530 | [diff] [blame] | 153 | u64 busy; |
| 154 | |
Oleg Perelet | b906a19 | 2017-01-04 09:50:02 -0800 | [diff] [blame] | 155 | spin_lock(&sample_lock); |
| 156 | /* |
| 157 | * Keep collecting the stats till the client |
| 158 | * reads it. Average of all samples and reset |
| 159 | * is done when the entry is read |
| 160 | */ |
| 161 | acc_total += stats->total_time; |
Harshitha Sai Neelati | 75ce3b1b | 2019-09-04 17:32:28 +0530 | [diff] [blame] | 162 | busy = (u64)stats->busy_time * stats->current_frequency; |
| 163 | do_div(busy, devfreq->profile->freq_table[0]); |
| 164 | acc_relative_busy += busy; |
| 165 | |
Oleg Perelet | b906a19 | 2017-01-04 09:50:02 -0800 | [diff] [blame] | 166 | spin_unlock(&sample_lock); |
| 167 | } |
| 168 | |
| 169 | /* Trap into the TrustZone, and call funcs there. */ |
| 170 | static int __secure_tz_reset_entry2(unsigned int *scm_data, u32 size_scm_data, |
| 171 | bool is_64) |
| 172 | { |
| 173 | int ret; |
| 174 | /* sync memory before sending the commands to tz */ |
| 175 | __iowmb(); |
| 176 | |
| 177 | if (!is_64) { |
| 178 | spin_lock(&tz_lock); |
| 179 | ret = scm_call_atomic2(SCM_SVC_IO, TZ_RESET_ID, scm_data[0], |
| 180 | scm_data[1]); |
| 181 | spin_unlock(&tz_lock); |
| 182 | } else { |
| 183 | if (is_scm_armv8()) { |
| 184 | struct scm_desc desc = {0}; |
| 185 | |
| 186 | desc.arginfo = 0; |
| 187 | ret = scm_call2(SCM_SIP_FNID(SCM_SVC_DCVS, |
| 188 | TZ_RESET_ID_64), &desc); |
| 189 | } else { |
| 190 | ret = scm_call(SCM_SVC_DCVS, TZ_RESET_ID_64, scm_data, |
| 191 | size_scm_data, NULL, 0); |
| 192 | } |
| 193 | } |
| 194 | return ret; |
| 195 | } |
| 196 | |
| 197 | static int __secure_tz_update_entry3(unsigned int *scm_data, u32 size_scm_data, |
| 198 | int *val, u32 size_val, struct devfreq_msm_adreno_tz_data *priv) |
| 199 | { |
| 200 | int ret; |
| 201 | /* sync memory before sending the commands to tz */ |
| 202 | __iowmb(); |
| 203 | |
| 204 | if (!priv->is_64) { |
| 205 | spin_lock(&tz_lock); |
| 206 | ret = scm_call_atomic3(SCM_SVC_IO, TZ_UPDATE_ID, |
| 207 | scm_data[0], scm_data[1], scm_data[2]); |
| 208 | spin_unlock(&tz_lock); |
| 209 | *val = ret; |
| 210 | } else { |
| 211 | if (is_scm_armv8()) { |
| 212 | unsigned int cmd_id; |
| 213 | struct scm_desc desc = {0}; |
| 214 | |
| 215 | desc.args[0] = scm_data[0]; |
| 216 | desc.args[1] = scm_data[1]; |
| 217 | desc.args[2] = scm_data[2]; |
| 218 | |
| 219 | if (!priv->ctxt_aware_enable) { |
| 220 | desc.arginfo = SCM_ARGS(3); |
| 221 | cmd_id = TZ_V2_UPDATE_ID_64; |
| 222 | } else { |
| 223 | /* Add context count infomration to update*/ |
| 224 | desc.args[3] = scm_data[3]; |
| 225 | desc.arginfo = SCM_ARGS(4); |
| 226 | cmd_id = TZ_V2_UPDATE_WITH_CA_ID_64; |
| 227 | } |
| 228 | ret = scm_call2(SCM_SIP_FNID(SCM_SVC_DCVS, cmd_id), |
| 229 | &desc); |
| 230 | *val = desc.ret[0]; |
| 231 | } else { |
| 232 | ret = scm_call(SCM_SVC_DCVS, TZ_UPDATE_ID_64, scm_data, |
| 233 | size_scm_data, val, size_val); |
| 234 | } |
| 235 | } |
| 236 | return ret; |
| 237 | } |
| 238 | |
| 239 | static int tz_init_ca(struct devfreq_msm_adreno_tz_data *priv) |
| 240 | { |
| 241 | unsigned int tz_ca_data[2]; |
| 242 | struct scm_desc desc = {0}; |
Deepak Kumar | b668b4e | 2017-01-25 18:53:55 +0530 | [diff] [blame] | 243 | u8 *tz_buf; |
Oleg Perelet | b906a19 | 2017-01-04 09:50:02 -0800 | [diff] [blame] | 244 | int ret; |
| 245 | |
| 246 | /* Set data for TZ */ |
| 247 | tz_ca_data[0] = priv->bin.ctxt_aware_target_pwrlevel; |
| 248 | tz_ca_data[1] = priv->bin.ctxt_aware_busy_penalty; |
| 249 | |
| 250 | tz_buf = kzalloc(PAGE_ALIGN(sizeof(tz_ca_data)), GFP_KERNEL); |
| 251 | if (!tz_buf) |
| 252 | return -ENOMEM; |
| 253 | |
| 254 | memcpy(tz_buf, tz_ca_data, sizeof(tz_ca_data)); |
| 255 | /* Ensure memcpy completes execution */ |
| 256 | mb(); |
| 257 | dmac_flush_range(tz_buf, |
| 258 | tz_buf + PAGE_ALIGN(sizeof(tz_ca_data))); |
| 259 | |
| 260 | desc.args[0] = virt_to_phys(tz_buf); |
| 261 | desc.args[1] = sizeof(tz_ca_data); |
| 262 | desc.arginfo = SCM_ARGS(2, SCM_RW, SCM_VAL); |
| 263 | |
| 264 | ret = scm_call2(SCM_SIP_FNID(SCM_SVC_DCVS, |
| 265 | TZ_V2_INIT_CA_ID_64), |
| 266 | &desc); |
| 267 | |
| 268 | kzfree(tz_buf); |
| 269 | |
| 270 | return ret; |
| 271 | } |
| 272 | |
| 273 | static int tz_init(struct devfreq_msm_adreno_tz_data *priv, |
| 274 | unsigned int *tz_pwrlevels, u32 size_pwrlevels, |
| 275 | unsigned int *version, u32 size_version) |
| 276 | { |
| 277 | int ret; |
| 278 | /* Make sure all CMD IDs are avaialble */ |
| 279 | if (scm_is_call_available(SCM_SVC_DCVS, TZ_INIT_ID)) { |
| 280 | ret = scm_call(SCM_SVC_DCVS, TZ_INIT_ID, tz_pwrlevels, |
| 281 | size_pwrlevels, NULL, 0); |
| 282 | *version = 0; |
| 283 | |
| 284 | } else if (scm_is_call_available(SCM_SVC_DCVS, TZ_INIT_ID_64) && |
| 285 | scm_is_call_available(SCM_SVC_DCVS, TZ_UPDATE_ID_64) && |
| 286 | scm_is_call_available(SCM_SVC_DCVS, TZ_RESET_ID_64)) { |
| 287 | struct scm_desc desc = {0}; |
Deepak Kumar | b668b4e | 2017-01-25 18:53:55 +0530 | [diff] [blame] | 288 | u8 *tz_buf; |
Oleg Perelet | b906a19 | 2017-01-04 09:50:02 -0800 | [diff] [blame] | 289 | |
| 290 | if (!is_scm_armv8()) { |
| 291 | ret = scm_call(SCM_SVC_DCVS, TZ_INIT_ID_64, |
| 292 | tz_pwrlevels, size_pwrlevels, |
| 293 | version, size_version); |
| 294 | if (!ret) |
| 295 | priv->is_64 = true; |
| 296 | return ret; |
| 297 | } |
| 298 | |
| 299 | tz_buf = kzalloc(PAGE_ALIGN(size_pwrlevels), GFP_KERNEL); |
| 300 | if (!tz_buf) |
| 301 | return -ENOMEM; |
| 302 | memcpy(tz_buf, tz_pwrlevels, size_pwrlevels); |
| 303 | /* Ensure memcpy completes execution */ |
| 304 | mb(); |
| 305 | dmac_flush_range(tz_buf, tz_buf + PAGE_ALIGN(size_pwrlevels)); |
| 306 | |
| 307 | desc.args[0] = virt_to_phys(tz_buf); |
| 308 | desc.args[1] = size_pwrlevels; |
| 309 | desc.arginfo = SCM_ARGS(2, SCM_RW, SCM_VAL); |
| 310 | |
| 311 | ret = scm_call2(SCM_SIP_FNID(SCM_SVC_DCVS, TZ_V2_INIT_ID_64), |
| 312 | &desc); |
| 313 | *version = desc.ret[0]; |
| 314 | if (!ret) |
| 315 | priv->is_64 = true; |
| 316 | kzfree(tz_buf); |
| 317 | } else |
| 318 | ret = -EINVAL; |
| 319 | |
| 320 | /* Initialize context aware feature, if enabled. */ |
| 321 | if (!ret && priv->ctxt_aware_enable) { |
| 322 | if (priv->is_64 && |
| 323 | (scm_is_call_available(SCM_SVC_DCVS, |
| 324 | TZ_V2_INIT_CA_ID_64)) && |
| 325 | (scm_is_call_available(SCM_SVC_DCVS, |
| 326 | TZ_V2_UPDATE_WITH_CA_ID_64))) { |
| 327 | ret = tz_init_ca(priv); |
| 328 | /* |
| 329 | * If context aware feature intialization fails, |
| 330 | * just print an error message and return |
| 331 | * success as normal DCVS will still work. |
| 332 | */ |
| 333 | if (ret) { |
| 334 | pr_err(TAG "tz: context aware DCVS init failed\n"); |
| 335 | priv->ctxt_aware_enable = false; |
| 336 | return 0; |
| 337 | } |
| 338 | } else { |
| 339 | pr_warn(TAG "tz: context aware DCVS not supported\n"); |
| 340 | priv->ctxt_aware_enable = false; |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | return ret; |
| 345 | } |
| 346 | |
| 347 | static inline int devfreq_get_freq_level(struct devfreq *devfreq, |
| 348 | unsigned long freq) |
| 349 | { |
| 350 | int lev; |
| 351 | |
| 352 | for (lev = 0; lev < devfreq->profile->max_state; lev++) |
| 353 | if (freq == devfreq->profile->freq_table[lev]) |
| 354 | return lev; |
| 355 | |
| 356 | return -EINVAL; |
| 357 | } |
| 358 | |
| 359 | static int tz_get_target_freq(struct devfreq *devfreq, unsigned long *freq) |
| 360 | { |
| 361 | int result = 0; |
| 362 | struct devfreq_msm_adreno_tz_data *priv = devfreq->data; |
| 363 | struct devfreq_dev_status stats; |
| 364 | int val, level = 0; |
| 365 | unsigned int scm_data[4]; |
| 366 | int context_count = 0; |
| 367 | |
| 368 | /* keeps stats.private_data == NULL */ |
| 369 | result = devfreq->profile->get_dev_status(devfreq->dev.parent, &stats); |
| 370 | if (result) { |
| 371 | pr_err(TAG "get_status failed %d\n", result); |
| 372 | return result; |
| 373 | } |
| 374 | |
| 375 | *freq = stats.current_frequency; |
| 376 | priv->bin.total_time += stats.total_time; |
| 377 | priv->bin.busy_time += stats.busy_time; |
| 378 | |
| 379 | if (stats.private_data) |
| 380 | context_count = *((int *)stats.private_data); |
| 381 | |
| 382 | /* Update the GPU load statistics */ |
| 383 | compute_work_load(&stats, priv, devfreq); |
| 384 | /* |
| 385 | * Do not waste CPU cycles running this algorithm if |
| 386 | * the GPU just started, or if less than FLOOR time |
| 387 | * has passed since the last run or the gpu hasn't been |
| 388 | * busier than MIN_BUSY. |
| 389 | */ |
| 390 | if ((stats.total_time == 0) || |
| 391 | (priv->bin.total_time < FLOOR) || |
| 392 | (unsigned int) priv->bin.busy_time < MIN_BUSY) { |
| 393 | return 0; |
| 394 | } |
| 395 | |
| 396 | level = devfreq_get_freq_level(devfreq, stats.current_frequency); |
| 397 | if (level < 0) { |
| 398 | pr_err(TAG "bad freq %ld\n", stats.current_frequency); |
| 399 | return level; |
| 400 | } |
| 401 | |
| 402 | /* |
| 403 | * If there is an extended block of busy processing, |
| 404 | * increase frequency. Otherwise run the normal algorithm. |
| 405 | */ |
| 406 | if (!priv->disable_busy_time_burst && |
| 407 | priv->bin.busy_time > CEILING) { |
| 408 | val = -1 * level; |
| 409 | } else { |
| 410 | |
| 411 | scm_data[0] = level; |
| 412 | scm_data[1] = priv->bin.total_time; |
| 413 | scm_data[2] = priv->bin.busy_time; |
| 414 | scm_data[3] = context_count; |
| 415 | __secure_tz_update_entry3(scm_data, sizeof(scm_data), |
| 416 | &val, sizeof(val), priv); |
| 417 | } |
| 418 | priv->bin.total_time = 0; |
| 419 | priv->bin.busy_time = 0; |
| 420 | |
| 421 | /* |
| 422 | * If the decision is to move to a different level, make sure the GPU |
| 423 | * frequency changes. |
| 424 | */ |
| 425 | if (val) { |
| 426 | level += val; |
| 427 | level = max(level, 0); |
| 428 | level = min_t(int, level, devfreq->profile->max_state - 1); |
| 429 | } |
| 430 | |
| 431 | *freq = devfreq->profile->freq_table[level]; |
| 432 | return 0; |
| 433 | } |
| 434 | |
| 435 | static int tz_notify(struct notifier_block *nb, unsigned long type, void *devp) |
| 436 | { |
| 437 | int result = 0; |
| 438 | struct devfreq *devfreq = devp; |
| 439 | |
| 440 | switch (type) { |
| 441 | case ADRENO_DEVFREQ_NOTIFY_IDLE: |
| 442 | case ADRENO_DEVFREQ_NOTIFY_RETIRE: |
| 443 | mutex_lock(&devfreq->lock); |
| 444 | result = update_devfreq(devfreq); |
| 445 | mutex_unlock(&devfreq->lock); |
| 446 | /* Nofifying partner bus governor if any */ |
| 447 | if (partner_gpu_profile && partner_gpu_profile->bus_devfreq) { |
| 448 | mutex_lock(&partner_gpu_profile->bus_devfreq->lock); |
| 449 | update_devfreq(partner_gpu_profile->bus_devfreq); |
| 450 | mutex_unlock(&partner_gpu_profile->bus_devfreq->lock); |
| 451 | } |
| 452 | break; |
| 453 | /* ignored by this governor */ |
| 454 | case ADRENO_DEVFREQ_NOTIFY_SUBMIT: |
| 455 | default: |
| 456 | break; |
| 457 | } |
| 458 | return notifier_from_errno(result); |
| 459 | } |
| 460 | |
| 461 | static int tz_start(struct devfreq *devfreq) |
| 462 | { |
| 463 | struct devfreq_msm_adreno_tz_data *priv; |
| 464 | unsigned int tz_pwrlevels[MSM_ADRENO_MAX_PWRLEVELS + 1]; |
| 465 | int i, out, ret; |
| 466 | unsigned int version; |
| 467 | |
| 468 | struct msm_adreno_extended_profile *gpu_profile = container_of( |
| 469 | (devfreq->profile), |
| 470 | struct msm_adreno_extended_profile, |
| 471 | profile); |
| 472 | |
| 473 | /* |
| 474 | * Assuming that we have only one instance of the adreno device |
| 475 | * connected to this governor, |
| 476 | * can safely restore the pointer to the governor private data |
| 477 | * from the container of the device profile |
| 478 | */ |
| 479 | devfreq->data = gpu_profile->private_data; |
| 480 | partner_gpu_profile = gpu_profile; |
| 481 | |
| 482 | priv = devfreq->data; |
| 483 | priv->nb.notifier_call = tz_notify; |
| 484 | |
| 485 | out = 1; |
| 486 | if (devfreq->profile->max_state < MSM_ADRENO_MAX_PWRLEVELS) { |
| 487 | for (i = 0; i < devfreq->profile->max_state; i++) |
| 488 | tz_pwrlevels[out++] = devfreq->profile->freq_table[i]; |
| 489 | tz_pwrlevels[0] = i; |
| 490 | } else { |
| 491 | pr_err(TAG "tz_pwrlevels[] is too short\n"); |
| 492 | return -EINVAL; |
| 493 | } |
| 494 | |
| 495 | INIT_WORK(&gpu_profile->partner_start_event_ws, |
| 496 | do_partner_start_event); |
| 497 | INIT_WORK(&gpu_profile->partner_stop_event_ws, |
| 498 | do_partner_stop_event); |
| 499 | INIT_WORK(&gpu_profile->partner_suspend_event_ws, |
| 500 | do_partner_suspend_event); |
| 501 | INIT_WORK(&gpu_profile->partner_resume_event_ws, |
| 502 | do_partner_resume_event); |
| 503 | |
| 504 | ret = tz_init(priv, tz_pwrlevels, sizeof(tz_pwrlevels), &version, |
| 505 | sizeof(version)); |
| 506 | if (ret != 0 || version > MAX_TZ_VERSION) { |
| 507 | pr_err(TAG "tz_init failed\n"); |
| 508 | return ret; |
| 509 | } |
| 510 | |
| 511 | for (i = 0; adreno_tz_attr_list[i] != NULL; i++) |
| 512 | device_create_file(&devfreq->dev, adreno_tz_attr_list[i]); |
| 513 | |
| 514 | return kgsl_devfreq_add_notifier(devfreq->dev.parent, &priv->nb); |
| 515 | } |
| 516 | |
| 517 | static int tz_stop(struct devfreq *devfreq) |
| 518 | { |
| 519 | int i; |
| 520 | struct devfreq_msm_adreno_tz_data *priv = devfreq->data; |
| 521 | |
| 522 | kgsl_devfreq_del_notifier(devfreq->dev.parent, &priv->nb); |
| 523 | |
| 524 | for (i = 0; adreno_tz_attr_list[i] != NULL; i++) |
| 525 | device_remove_file(&devfreq->dev, adreno_tz_attr_list[i]); |
| 526 | |
| 527 | flush_workqueue(workqueue); |
| 528 | |
| 529 | /* leaving the governor and cleaning the pointer to private data */ |
| 530 | devfreq->data = NULL; |
| 531 | partner_gpu_profile = NULL; |
| 532 | return 0; |
| 533 | } |
| 534 | |
| 535 | static int tz_suspend(struct devfreq *devfreq) |
| 536 | { |
| 537 | struct devfreq_msm_adreno_tz_data *priv = devfreq->data; |
| 538 | unsigned int scm_data[2] = {0, 0}; |
| 539 | |
| 540 | __secure_tz_reset_entry2(scm_data, sizeof(scm_data), priv->is_64); |
| 541 | |
| 542 | priv->bin.total_time = 0; |
| 543 | priv->bin.busy_time = 0; |
| 544 | return 0; |
| 545 | } |
| 546 | |
| 547 | static int tz_handler(struct devfreq *devfreq, unsigned int event, void *data) |
| 548 | { |
| 549 | int result; |
| 550 | |
| 551 | struct msm_adreno_extended_profile *gpu_profile = container_of( |
| 552 | (devfreq->profile), |
| 553 | struct msm_adreno_extended_profile, |
| 554 | profile); |
Oleg Perelet | b906a19 | 2017-01-04 09:50:02 -0800 | [diff] [blame] | 555 | |
| 556 | switch (event) { |
| 557 | case DEVFREQ_GOV_START: |
| 558 | result = tz_start(devfreq); |
| 559 | break; |
| 560 | |
| 561 | case DEVFREQ_GOV_STOP: |
| 562 | /* Queue the stop work before the TZ is stopped */ |
| 563 | if (partner_gpu_profile && partner_gpu_profile->bus_devfreq) |
| 564 | queue_work(workqueue, |
| 565 | &gpu_profile->partner_stop_event_ws); |
| 566 | spin_lock(&suspend_lock); |
| 567 | suspend_start = 0; |
| 568 | spin_unlock(&suspend_lock); |
| 569 | result = tz_stop(devfreq); |
| 570 | break; |
| 571 | |
| 572 | case DEVFREQ_GOV_SUSPEND: |
| 573 | result = tz_suspend(devfreq); |
| 574 | if (!result) { |
| 575 | spin_lock(&suspend_lock); |
| 576 | /* Collect the start sample for suspend time */ |
| 577 | suspend_start = (u64)ktime_to_ms(ktime_get()); |
| 578 | spin_unlock(&suspend_lock); |
| 579 | } |
| 580 | break; |
| 581 | |
| 582 | case DEVFREQ_GOV_RESUME: |
| 583 | spin_lock(&suspend_lock); |
| 584 | suspend_time += suspend_time_ms(); |
| 585 | /* Reset the suspend_start when gpu resumes */ |
| 586 | suspend_start = 0; |
| 587 | spin_unlock(&suspend_lock); |
| 588 | /* fallthrough */ |
| 589 | case DEVFREQ_GOV_INTERVAL: |
| 590 | /* fallthrough, this governor doesn't use polling */ |
| 591 | default: |
| 592 | result = 0; |
| 593 | break; |
| 594 | } |
| 595 | |
| 596 | if (partner_gpu_profile && partner_gpu_profile->bus_devfreq) |
| 597 | switch (event) { |
| 598 | case DEVFREQ_GOV_START: |
| 599 | queue_work(workqueue, |
| 600 | &gpu_profile->partner_start_event_ws); |
| 601 | break; |
| 602 | case DEVFREQ_GOV_SUSPEND: |
| 603 | queue_work(workqueue, |
| 604 | &gpu_profile->partner_suspend_event_ws); |
| 605 | break; |
| 606 | case DEVFREQ_GOV_RESUME: |
| 607 | queue_work(workqueue, |
| 608 | &gpu_profile->partner_resume_event_ws); |
| 609 | break; |
| 610 | } |
| 611 | |
| 612 | return result; |
| 613 | } |
| 614 | |
| 615 | static void _do_partner_event(struct work_struct *work, unsigned int event) |
| 616 | { |
| 617 | struct devfreq *bus_devfreq; |
| 618 | |
| 619 | if (partner_gpu_profile == NULL) |
| 620 | return; |
| 621 | |
| 622 | bus_devfreq = partner_gpu_profile->bus_devfreq; |
| 623 | |
| 624 | if (bus_devfreq != NULL && |
| 625 | bus_devfreq->governor && |
| 626 | bus_devfreq->governor->event_handler) |
| 627 | bus_devfreq->governor->event_handler(bus_devfreq, event, NULL); |
| 628 | } |
| 629 | |
| 630 | static void do_partner_start_event(struct work_struct *work) |
| 631 | { |
| 632 | _do_partner_event(work, DEVFREQ_GOV_START); |
| 633 | } |
| 634 | |
| 635 | static void do_partner_stop_event(struct work_struct *work) |
| 636 | { |
| 637 | _do_partner_event(work, DEVFREQ_GOV_STOP); |
| 638 | } |
| 639 | |
| 640 | static void do_partner_suspend_event(struct work_struct *work) |
| 641 | { |
| 642 | _do_partner_event(work, DEVFREQ_GOV_SUSPEND); |
| 643 | } |
| 644 | |
| 645 | static void do_partner_resume_event(struct work_struct *work) |
| 646 | { |
| 647 | _do_partner_event(work, DEVFREQ_GOV_RESUME); |
| 648 | } |
| 649 | |
| 650 | |
| 651 | static struct devfreq_governor msm_adreno_tz = { |
| 652 | .name = "msm-adreno-tz", |
| 653 | .get_target_freq = tz_get_target_freq, |
| 654 | .event_handler = tz_handler, |
| 655 | }; |
| 656 | |
| 657 | static int __init msm_adreno_tz_init(void) |
| 658 | { |
| 659 | workqueue = create_freezable_workqueue("governor_msm_adreno_tz_wq"); |
| 660 | |
| 661 | if (workqueue == NULL) |
| 662 | return -ENOMEM; |
| 663 | |
| 664 | return devfreq_add_governor(&msm_adreno_tz); |
| 665 | } |
| 666 | subsys_initcall(msm_adreno_tz_init); |
| 667 | |
| 668 | static void __exit msm_adreno_tz_exit(void) |
| 669 | { |
| 670 | int ret = devfreq_remove_governor(&msm_adreno_tz); |
| 671 | |
| 672 | if (ret) |
| 673 | pr_err(TAG "failed to remove governor %d\n", ret); |
| 674 | |
| 675 | if (workqueue != NULL) |
| 676 | destroy_workqueue(workqueue); |
| 677 | } |
| 678 | |
| 679 | module_exit(msm_adreno_tz_exit); |
| 680 | |
| 681 | MODULE_LICENSE("GPL v2"); |