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