Matt Wagantall | 488bef3 | 2012-07-13 19:42:11 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2012, The Linux Foundation. All rights reserved. |
| 3 | * |
| 4 | * This program is free software; you can redistribute it and/or modify |
| 5 | * it under the terms of the GNU General Public License version 2 and |
| 6 | * only version 2 as published by the Free Software Foundation. |
| 7 | * |
| 8 | * This program is distributed in the hope that it will be useful, |
| 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | * GNU General Public License for more details. |
| 12 | */ |
| 13 | |
| 14 | #define pr_fmt(fmt) "%s: " fmt, __func__ |
| 15 | |
| 16 | #include <linux/kernel.h> |
| 17 | #include <linux/io.h> |
| 18 | #include <linux/debugfs.h> |
| 19 | #include <linux/module.h> |
| 20 | #include <linux/mutex.h> |
| 21 | #include <linux/err.h> |
| 22 | #include <linux/errno.h> |
| 23 | #include <linux/cpu.h> |
| 24 | #include <linux/smp.h> |
| 25 | |
| 26 | #include <mach/msm_bus.h> |
| 27 | #include <mach/msm-krait-l2-accessors.h> |
| 28 | |
| 29 | #include "acpuclock-krait.h" |
| 30 | |
| 31 | static struct drv_data *drv; |
| 32 | static DEFINE_MUTEX(debug_lock); |
| 33 | |
| 34 | struct acg_action { |
| 35 | bool set; |
| 36 | bool enable; |
| 37 | }; |
Matt Wagantall | aaef70a | 2012-10-15 19:35:19 -0700 | [diff] [blame] | 38 | static int l2_acg_en_val; |
Matt Wagantall | 488bef3 | 2012-07-13 19:42:11 -0700 | [diff] [blame] | 39 | static struct dentry *base_dir; |
| 40 | static struct dentry *sc_dir[MAX_SCALABLES]; |
| 41 | |
| 42 | static void cpu_action(void *info) |
| 43 | { |
| 44 | struct acg_action *action = info; |
| 45 | |
| 46 | u32 val; |
| 47 | asm volatile ("mrc p15, 7, %[cpmr0], c15, c0, 5\n\t" |
| 48 | : [cpmr0]"=r" (val)); |
| 49 | if (action->set) { |
| 50 | if (action->enable) |
| 51 | val &= ~BIT(0); |
| 52 | else |
| 53 | val |= BIT(0); |
Matt Wagantall | aaef70a | 2012-10-15 19:35:19 -0700 | [diff] [blame] | 54 | asm volatile ("mcr p15, 7, %[cpmr0], c15, c0, 5\n\t" |
| 55 | : : [cpmr0]"r" (val)); |
Matt Wagantall | 488bef3 | 2012-07-13 19:42:11 -0700 | [diff] [blame] | 56 | } else { |
| 57 | action->enable = !(val & BIT(0)); |
| 58 | } |
| 59 | } |
| 60 | |
| 61 | /* Disable auto clock-gating for a scalable. */ |
| 62 | static void disable_acg(int sc_id) |
| 63 | { |
| 64 | u32 regval; |
| 65 | |
| 66 | if (sc_id == L2) { |
| 67 | regval = get_l2_indirect_reg(drv->scalable[sc_id].l2cpmr_iaddr); |
Matt Wagantall | aaef70a | 2012-10-15 19:35:19 -0700 | [diff] [blame] | 68 | l2_acg_en_val = regval & (0x3 << 10); |
Matt Wagantall | 488bef3 | 2012-07-13 19:42:11 -0700 | [diff] [blame] | 69 | regval |= (0x3 << 10); |
| 70 | set_l2_indirect_reg(drv->scalable[sc_id].l2cpmr_iaddr, regval); |
| 71 | } else { |
| 72 | struct acg_action action = { .set = true, .enable = false }; |
| 73 | smp_call_function_single(sc_id, cpu_action, &action, 1); |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | /* Enable auto clock-gating for a scalable. */ |
| 78 | static void enable_acg(int sc_id) |
| 79 | { |
| 80 | u32 regval; |
| 81 | |
| 82 | if (sc_id == L2) { |
| 83 | regval = get_l2_indirect_reg(drv->scalable[sc_id].l2cpmr_iaddr); |
| 84 | regval &= ~(0x3 << 10); |
Matt Wagantall | aaef70a | 2012-10-15 19:35:19 -0700 | [diff] [blame] | 85 | regval |= l2_acg_en_val; |
Matt Wagantall | 488bef3 | 2012-07-13 19:42:11 -0700 | [diff] [blame] | 86 | set_l2_indirect_reg(drv->scalable[sc_id].l2cpmr_iaddr, regval); |
| 87 | } else { |
| 88 | struct acg_action action = { .set = true, .enable = true }; |
| 89 | smp_call_function_single(sc_id, cpu_action, &action, 1); |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | /* Check if auto clock-gating for a scalable. */ |
| 94 | static bool acg_is_enabled(int sc_id) |
| 95 | { |
| 96 | u32 regval; |
| 97 | |
| 98 | if (sc_id == L2) { |
| 99 | regval = get_l2_indirect_reg(drv->scalable[sc_id].l2cpmr_iaddr); |
| 100 | return ((regval >> 10) & 0x3) != 0x3; |
| 101 | } else { |
| 102 | struct acg_action action = { .set = false }; |
| 103 | smp_call_function_single(sc_id, cpu_action, &action, 1); |
| 104 | return action.enable; |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | /* Enable/Disable auto clock gating. */ |
| 109 | static int acg_set(void *data, u64 val) |
| 110 | { |
| 111 | int ret = 0; |
| 112 | int sc_id = (int)data; |
| 113 | |
| 114 | mutex_lock(&debug_lock); |
| 115 | get_online_cpus(); |
| 116 | if (!sc_dir[sc_id]) { |
| 117 | ret = -ENODEV; |
| 118 | goto out; |
| 119 | } |
| 120 | |
| 121 | if (val == 0 && acg_is_enabled(sc_id)) |
| 122 | disable_acg(sc_id); |
| 123 | else if (val == 1) |
| 124 | enable_acg(sc_id); |
| 125 | out: |
| 126 | put_online_cpus(); |
| 127 | mutex_unlock(&debug_lock); |
| 128 | |
| 129 | return ret; |
| 130 | } |
| 131 | |
| 132 | /* Get auto clock-gating state. */ |
| 133 | static int acg_get(void *data, u64 *val) |
| 134 | { |
| 135 | int ret = 0; |
| 136 | int sc_id = (int)data; |
| 137 | |
| 138 | mutex_lock(&debug_lock); |
| 139 | get_online_cpus(); |
| 140 | if (!sc_dir[sc_id]) { |
| 141 | ret = -ENODEV; |
| 142 | goto out; |
| 143 | } |
| 144 | |
| 145 | *val = acg_is_enabled(sc_id); |
| 146 | out: |
| 147 | put_online_cpus(); |
| 148 | mutex_unlock(&debug_lock); |
| 149 | |
| 150 | return ret; |
| 151 | } |
| 152 | DEFINE_SIMPLE_ATTRIBUTE(acgd_fops, acg_get, acg_set, "%lld\n"); |
| 153 | |
| 154 | /* Get the rate */ |
| 155 | static int rate_get(void *data, u64 *val) |
| 156 | { |
| 157 | int sc_id = (int)data; |
| 158 | *val = drv->scalable[sc_id].cur_speed->khz; |
| 159 | return 0; |
| 160 | } |
| 161 | DEFINE_SIMPLE_ATTRIBUTE(rate_fops, rate_get, NULL, "%lld\n"); |
| 162 | |
| 163 | /* Get the HFPLL's L-value. */ |
| 164 | static int hfpll_l_get(void *data, u64 *val) |
| 165 | { |
| 166 | int sc_id = (int)data; |
| 167 | *val = drv->scalable[sc_id].cur_speed->pll_l_val; |
| 168 | return 0; |
| 169 | } |
| 170 | DEFINE_SIMPLE_ATTRIBUTE(hfpll_l_fops, hfpll_l_get, NULL, "%lld\n"); |
| 171 | |
| 172 | /* Get the L2 rate vote. */ |
| 173 | static int l2_vote_get(void *data, u64 *val) |
| 174 | { |
| 175 | int level, sc_id = (int)data; |
| 176 | |
| 177 | level = drv->scalable[sc_id].l2_vote; |
| 178 | *val = drv->l2_freq_tbl[level].speed.khz; |
| 179 | |
| 180 | return 0; |
| 181 | } |
| 182 | DEFINE_SIMPLE_ATTRIBUTE(l2_vote_fops, l2_vote_get, NULL, "%lld\n"); |
| 183 | |
| 184 | /* Get the bandwidth vote. */ |
| 185 | static int bw_vote_get(void *data, u64 *val) |
| 186 | { |
| 187 | struct l2_level *l; |
| 188 | |
| 189 | l = container_of(drv->scalable[L2].cur_speed, |
| 190 | struct l2_level, speed); |
| 191 | *val = drv->bus_scale->usecase[l->bw_level].vectors->ib; |
| 192 | |
| 193 | return 0; |
| 194 | } |
| 195 | DEFINE_SIMPLE_ATTRIBUTE(bw_vote_fops, bw_vote_get, NULL, "%lld\n"); |
| 196 | |
| 197 | /* Get the name of the currently-selected clock source. */ |
| 198 | static int src_name_show(struct seq_file *m, void *unused) |
| 199 | { |
| 200 | const char *const src_names[NUM_SRC_ID] = { |
| 201 | [PLL_0] = "PLL0", |
| 202 | [HFPLL] = "HFPLL", |
| 203 | [PLL_8] = "PLL8", |
| 204 | }; |
| 205 | int src, sc_id = (int)m->private; |
| 206 | |
| 207 | src = drv->scalable[sc_id].cur_speed->src; |
| 208 | if (src > ARRAY_SIZE(src_names)) |
| 209 | return -EINVAL; |
| 210 | |
| 211 | seq_printf(m, "%s\n", src_names[src]); |
| 212 | |
| 213 | return 0; |
| 214 | } |
| 215 | |
| 216 | static int src_name_open(struct inode *inode, struct file *file) |
| 217 | { |
| 218 | return single_open(file, src_name_show, inode->i_private); |
| 219 | } |
| 220 | |
| 221 | static const struct file_operations src_name_fops = { |
| 222 | .open = src_name_open, |
| 223 | .read = seq_read, |
| 224 | .llseek = seq_lseek, |
| 225 | .release = seq_release, |
| 226 | }; |
| 227 | |
| 228 | /* Get speed_bin ID */ |
| 229 | static int speed_bin_get(void *data, u64 *val) |
| 230 | { |
| 231 | *val = drv->speed_bin; |
| 232 | return 0; |
| 233 | } |
| 234 | DEFINE_SIMPLE_ATTRIBUTE(speed_bin_fops, speed_bin_get, NULL, "%lld\n"); |
| 235 | |
| 236 | /* Get pvs_bin ID */ |
| 237 | static int pvs_bin_get(void *data, u64 *val) |
| 238 | { |
| 239 | *val = drv->pvs_bin; |
| 240 | return 0; |
| 241 | } |
| 242 | DEFINE_SIMPLE_ATTRIBUTE(pvs_bin_fops, pvs_bin_get, NULL, "%lld\n"); |
| 243 | |
| 244 | /* Get boost_uv */ |
| 245 | static int boost_get(void *data, u64 *val) |
| 246 | { |
| 247 | *val = drv->boost_uv; |
| 248 | return 0; |
| 249 | } |
| 250 | DEFINE_SIMPLE_ATTRIBUTE(boost_fops, boost_get, NULL, "%lld\n"); |
| 251 | |
Vikram Mulukutla | e81c14d | 2013-03-15 17:21:23 -0700 | [diff] [blame] | 252 | static int acpu_table_show(struct seq_file *m, void *unused) |
| 253 | { |
| 254 | const struct acpu_level *level; |
| 255 | |
| 256 | seq_printf(m, "CPU_KHz PLL_L_Val L2_KHz VDD_Dig VDD_Mem "); |
| 257 | seq_printf(m, "BW_Mbps VDD_Core UA_Core AVS\n"); |
| 258 | |
| 259 | for (level = drv->acpu_freq_tbl; level->speed.khz != 0; level++) { |
| 260 | |
| 261 | const struct l2_level *l2 = |
| 262 | &drv->l2_freq_tbl[level->l2_level]; |
| 263 | u32 bw = drv->bus_scale->usecase[l2->bw_level].vectors[0].ib; |
| 264 | |
| 265 | if (!level->use_for_scaling) |
| 266 | continue; |
| 267 | |
| 268 | /* CPU speed information */ |
| 269 | seq_printf(m, "%7lu %9u ", |
| 270 | level->speed.khz, |
| 271 | level->speed.pll_l_val); |
| 272 | |
| 273 | /* L2 level information */ |
| 274 | seq_printf(m, "%7lu %7d %7d %7u ", |
| 275 | l2->speed.khz, |
| 276 | l2->vdd_dig, |
| 277 | l2->vdd_mem, |
| 278 | bw / 1000000); |
| 279 | |
| 280 | /* Core voltage information */ |
| 281 | seq_printf(m, "%8d %7d %3d\n", |
| 282 | level->vdd_core, |
| 283 | level->ua_core, |
| 284 | level->avsdscr_setting); |
| 285 | } |
| 286 | |
| 287 | return 0; |
| 288 | } |
| 289 | |
| 290 | static int acpu_table_open(struct inode *inode, struct file *file) |
| 291 | { |
| 292 | return single_open(file, acpu_table_show, inode->i_private); |
| 293 | } |
| 294 | |
| 295 | static const struct file_operations acpu_table_fops = { |
| 296 | .open = acpu_table_open, |
| 297 | .read = seq_read, |
| 298 | .llseek = seq_lseek, |
| 299 | .release = seq_release, |
| 300 | }; |
| 301 | |
Matt Wagantall | 488bef3 | 2012-07-13 19:42:11 -0700 | [diff] [blame] | 302 | static void __cpuinit add_scalable_dir(int sc_id) |
| 303 | { |
| 304 | char sc_name[8]; |
| 305 | |
| 306 | if (sc_id == L2) |
| 307 | snprintf(sc_name, sizeof(sc_name), "l2"); |
| 308 | else |
| 309 | snprintf(sc_name, sizeof(sc_name), "cpu%d", sc_id); |
| 310 | |
| 311 | sc_dir[sc_id] = debugfs_create_dir(sc_name, base_dir); |
| 312 | if (!sc_dir[sc_id]) |
| 313 | return; |
| 314 | |
| 315 | debugfs_create_file("auto_gating", S_IRUGO | S_IWUSR, |
| 316 | sc_dir[sc_id], (void *)sc_id, &acgd_fops); |
| 317 | |
| 318 | debugfs_create_file("rate", S_IRUGO, |
| 319 | sc_dir[sc_id], (void *)sc_id, &rate_fops); |
| 320 | |
| 321 | debugfs_create_file("hfpll_l", S_IRUGO, |
| 322 | sc_dir[sc_id], (void *)sc_id, &hfpll_l_fops); |
| 323 | |
| 324 | debugfs_create_file("src", S_IRUGO, |
| 325 | sc_dir[sc_id], (void *)sc_id, &src_name_fops); |
| 326 | |
| 327 | if (sc_id == L2) |
| 328 | debugfs_create_file("bw_ib_vote", S_IRUGO, |
| 329 | sc_dir[sc_id], (void *)sc_id, &bw_vote_fops); |
| 330 | else |
| 331 | debugfs_create_file("l2_vote", S_IRUGO, |
| 332 | sc_dir[sc_id], (void *)sc_id, &l2_vote_fops); |
| 333 | } |
| 334 | |
| 335 | static void __cpuinit remove_scalable_dir(int sc_id) |
| 336 | { |
| 337 | debugfs_remove_recursive(sc_dir[sc_id]); |
| 338 | sc_dir[sc_id] = NULL; |
| 339 | } |
| 340 | |
| 341 | static int __cpuinit debug_cpu_callback(struct notifier_block *nfb, |
| 342 | unsigned long action, void *hcpu) |
| 343 | { |
| 344 | int cpu = (int)hcpu; |
| 345 | |
| 346 | switch (action & ~CPU_TASKS_FROZEN) { |
| 347 | case CPU_DOWN_FAILED: |
| 348 | case CPU_UP_PREPARE: |
| 349 | add_scalable_dir(cpu); |
| 350 | break; |
| 351 | case CPU_UP_CANCELED: |
| 352 | case CPU_DOWN_PREPARE: |
| 353 | remove_scalable_dir(cpu); |
| 354 | break; |
| 355 | default: |
| 356 | break; |
| 357 | } |
| 358 | |
| 359 | return NOTIFY_OK; |
| 360 | } |
| 361 | |
| 362 | static struct notifier_block __cpuinitdata debug_cpu_notifier = { |
| 363 | .notifier_call = debug_cpu_callback, |
| 364 | }; |
| 365 | |
| 366 | void __init acpuclk_krait_debug_init(struct drv_data *drv_data) |
| 367 | { |
| 368 | int cpu; |
| 369 | drv = drv_data; |
| 370 | |
| 371 | base_dir = debugfs_create_dir("acpuclk", NULL); |
| 372 | if (!base_dir) |
| 373 | return; |
| 374 | |
| 375 | debugfs_create_file("speed_bin", S_IRUGO, base_dir, NULL, |
| 376 | &speed_bin_fops); |
| 377 | debugfs_create_file("pvs_bin", S_IRUGO, base_dir, NULL, &pvs_bin_fops); |
| 378 | debugfs_create_file("boost_uv", S_IRUGO, base_dir, NULL, &boost_fops); |
Vikram Mulukutla | e81c14d | 2013-03-15 17:21:23 -0700 | [diff] [blame] | 379 | debugfs_create_file("acpu_table", S_IRUGO, base_dir, NULL, |
| 380 | &acpu_table_fops); |
Matt Wagantall | 488bef3 | 2012-07-13 19:42:11 -0700 | [diff] [blame] | 381 | |
| 382 | for_each_online_cpu(cpu) |
| 383 | add_scalable_dir(cpu); |
| 384 | add_scalable_dir(L2); |
| 385 | |
| 386 | register_hotcpu_notifier(&debug_cpu_notifier); |
| 387 | } |