1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright (C) 2007 PA Semi, Inc 4 * 5 * Authors: Egor Martovetsky <egor@pasemi.com> 6 * Olof Johansson <olof@lixom.net> 7 * 8 * Maintained by: Olof Johansson <olof@lixom.net> 9 * 10 * Based on arch/powerpc/platforms/cell/cbe_cpufreq.c: 11 * (C) Copyright IBM Deutschland Entwicklung GmbH 2005 12 */ 13 14 #include <linux/cpufreq.h> 15 #include <linux/timer.h> 16 #include <linux/module.h> 17 #include <linux/of_address.h> 18 19 #include <asm/hw_irq.h> 20 #include <asm/io.h> 21 #include <asm/prom.h> 22 #include <asm/time.h> 23 #include <asm/smp.h> 24 25 #define SDCASR_REG 0x0100 26 #define SDCASR_REG_STRIDE 0x1000 27 #define SDCPWR_CFGA0_REG 0x0100 28 #define SDCPWR_PWST0_REG 0x0000 29 #define SDCPWR_GIZTIME_REG 0x0440 30 31 /* SDCPWR_GIZTIME_REG fields */ 32 #define SDCPWR_GIZTIME_GR 0x80000000 33 #define SDCPWR_GIZTIME_LONGLOCK 0x000000ff 34 35 /* Offset of ASR registers from SDC base */ 36 #define SDCASR_OFFSET 0x120000 37 38 static void __iomem *sdcpwr_mapbase; 39 static void __iomem *sdcasr_mapbase; 40 41 /* Current astate, is used when waking up from power savings on 42 * one core, in case the other core has switched states during 43 * the idle time. 44 */ 45 static int current_astate; 46 47 /* We support 5(A0-A4) power states excluding turbo(A5-A6) modes */ 48 static struct cpufreq_frequency_table pas_freqs[] = { 49 {0, 0, 0}, 50 {0, 1, 0}, 51 {0, 2, 0}, 52 {0, 3, 0}, 53 {0, 4, 0}, 54 {0, 0, CPUFREQ_TABLE_END}, 55 }; 56 57 /* 58 * hardware specific functions 59 */ 60 61 static int get_astate_freq(int astate) 62 { 63 u32 ret; 64 ret = in_le32(sdcpwr_mapbase + SDCPWR_CFGA0_REG + (astate * 0x10)); 65 66 return ret & 0x3f; 67 } 68 69 static int get_cur_astate(int cpu) 70 { 71 u32 ret; 72 73 ret = in_le32(sdcpwr_mapbase + SDCPWR_PWST0_REG); 74 ret = (ret >> (cpu * 4)) & 0x7; 75 76 return ret; 77 } 78 79 static int get_gizmo_latency(void) 80 { 81 u32 giztime, ret; 82 83 giztime = in_le32(sdcpwr_mapbase + SDCPWR_GIZTIME_REG); 84 85 /* just provide the upper bound */ 86 if (giztime & SDCPWR_GIZTIME_GR) 87 ret = (giztime & SDCPWR_GIZTIME_LONGLOCK) * 128000; 88 else 89 ret = (giztime & SDCPWR_GIZTIME_LONGLOCK) * 1000; 90 91 return ret; 92 } 93 94 static void set_astate(int cpu, unsigned int astate) 95 { 96 unsigned long flags; 97 98 /* Return if called before init has run */ 99 if (unlikely(!sdcasr_mapbase)) 100 return; 101 102 local_irq_save(flags); 103 104 out_le32(sdcasr_mapbase + SDCASR_REG + SDCASR_REG_STRIDE*cpu, astate); 105 106 local_irq_restore(flags); 107 } 108 109 int check_astate(void) 110 { 111 return get_cur_astate(hard_smp_processor_id()); 112 } 113 114 void restore_astate(int cpu) 115 { 116 set_astate(cpu, current_astate); 117 } 118 119 /* 120 * cpufreq functions 121 */ 122 123 static int pas_cpufreq_cpu_init(struct cpufreq_policy *policy) 124 { 125 struct cpufreq_frequency_table *pos; 126 const u32 *max_freqp; 127 u32 max_freq; 128 int cur_astate, idx; 129 struct resource res; 130 struct device_node *cpu, *dn; 131 int err = -ENODEV; 132 133 cpu = of_get_cpu_node(policy->cpu, NULL); 134 135 of_node_put(cpu); 136 if (!cpu) 137 goto out; 138 139 dn = of_find_compatible_node(NULL, NULL, "1682m-sdc"); 140 if (!dn) 141 dn = of_find_compatible_node(NULL, NULL, 142 "pasemi,pwrficient-sdc"); 143 if (!dn) 144 goto out; 145 err = of_address_to_resource(dn, 0, &res); 146 of_node_put(dn); 147 if (err) 148 goto out; 149 sdcasr_mapbase = ioremap(res.start + SDCASR_OFFSET, 0x2000); 150 if (!sdcasr_mapbase) { 151 err = -EINVAL; 152 goto out; 153 } 154 155 dn = of_find_compatible_node(NULL, NULL, "1682m-gizmo"); 156 if (!dn) 157 dn = of_find_compatible_node(NULL, NULL, 158 "pasemi,pwrficient-gizmo"); 159 if (!dn) { 160 err = -ENODEV; 161 goto out_unmap_sdcasr; 162 } 163 err = of_address_to_resource(dn, 0, &res); 164 of_node_put(dn); 165 if (err) 166 goto out_unmap_sdcasr; 167 sdcpwr_mapbase = ioremap(res.start, 0x1000); 168 if (!sdcpwr_mapbase) { 169 err = -EINVAL; 170 goto out_unmap_sdcasr; 171 } 172 173 pr_debug("init cpufreq on CPU %d\n", policy->cpu); 174 175 max_freqp = of_get_property(cpu, "clock-frequency", NULL); 176 if (!max_freqp) { 177 err = -EINVAL; 178 goto out_unmap_sdcpwr; 179 } 180 181 /* we need the freq in kHz */ 182 max_freq = *max_freqp / 1000; 183 184 pr_debug("max clock-frequency is at %u kHz\n", max_freq); 185 pr_debug("initializing frequency table\n"); 186 187 /* initialize frequency table */ 188 cpufreq_for_each_entry_idx(pos, pas_freqs, idx) { 189 pos->frequency = get_astate_freq(pos->driver_data) * 100000; 190 pr_debug("%d: %d\n", idx, pos->frequency); 191 } 192 193 cur_astate = get_cur_astate(policy->cpu); 194 pr_debug("current astate is at %d\n",cur_astate); 195 196 policy->cur = pas_freqs[cur_astate].frequency; 197 ppc_proc_freq = policy->cur * 1000ul; 198 199 cpufreq_generic_init(policy, pas_freqs, get_gizmo_latency()); 200 return 0; 201 202 out_unmap_sdcpwr: 203 iounmap(sdcpwr_mapbase); 204 205 out_unmap_sdcasr: 206 iounmap(sdcasr_mapbase); 207 out: 208 return err; 209 } 210 211 static int pas_cpufreq_cpu_exit(struct cpufreq_policy *policy) 212 { 213 /* 214 * We don't support CPU hotplug. Don't unmap after the system 215 * has already made it to a running state. 216 */ 217 if (system_state >= SYSTEM_RUNNING) 218 return 0; 219 220 if (sdcasr_mapbase) 221 iounmap(sdcasr_mapbase); 222 if (sdcpwr_mapbase) 223 iounmap(sdcpwr_mapbase); 224 225 return 0; 226 } 227 228 static int pas_cpufreq_target(struct cpufreq_policy *policy, 229 unsigned int pas_astate_new) 230 { 231 int i; 232 233 pr_debug("setting frequency for cpu %d to %d kHz, 1/%d of max frequency\n", 234 policy->cpu, 235 pas_freqs[pas_astate_new].frequency, 236 pas_freqs[pas_astate_new].driver_data); 237 238 current_astate = pas_astate_new; 239 240 for_each_online_cpu(i) 241 set_astate(i, pas_astate_new); 242 243 ppc_proc_freq = pas_freqs[pas_astate_new].frequency * 1000ul; 244 return 0; 245 } 246 247 static struct cpufreq_driver pas_cpufreq_driver = { 248 .name = "pas-cpufreq", 249 .flags = CPUFREQ_CONST_LOOPS, 250 .init = pas_cpufreq_cpu_init, 251 .exit = pas_cpufreq_cpu_exit, 252 .verify = cpufreq_generic_frequency_table_verify, 253 .target_index = pas_cpufreq_target, 254 .attr = cpufreq_generic_attr, 255 }; 256 257 /* 258 * module init and destoy 259 */ 260 261 static int __init pas_cpufreq_init(void) 262 { 263 if (!of_machine_is_compatible("PA6T-1682M") && 264 !of_machine_is_compatible("pasemi,pwrficient")) 265 return -ENODEV; 266 267 return cpufreq_register_driver(&pas_cpufreq_driver); 268 } 269 270 static void __exit pas_cpufreq_exit(void) 271 { 272 cpufreq_unregister_driver(&pas_cpufreq_driver); 273 } 274 275 module_init(pas_cpufreq_init); 276 module_exit(pas_cpufreq_exit); 277 278 MODULE_LICENSE("GPL"); 279 MODULE_AUTHOR("Egor Martovetsky <egor@pasemi.com>, Olof Johansson <olof@lixom.net>"); 280