1 /* 2 * PowerPC 4xx Clock and Power Management 3 * 4 * Copyright (C) 2010, Applied Micro Circuits Corporation 5 * Victor Gallardo (vgallardo@apm.com) 6 * 7 * Based on arch/powerpc/platforms/44x/idle.c: 8 * Jerone Young <jyoung5@us.ibm.com> 9 * Copyright 2008 IBM Corp. 10 * 11 * Based on arch/powerpc/sysdev/fsl_pmc.c: 12 * Anton Vorontsov <avorontsov@ru.mvista.com> 13 * Copyright 2009 MontaVista Software, Inc. 14 * 15 * See file CREDITS for list of people who contributed to this 16 * project. 17 * 18 * This program is free software; you can redistribute it and/or 19 * modify it under the terms of the GNU General Public License as 20 * published by the Free Software Foundation; either version 2 of 21 * the License, or (at your option) any later version. 22 * 23 * This program is distributed in the hope that it will be useful, 24 * but WITHOUT ANY WARRANTY; without even the implied warranty of 25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 26 * GNU General Public License for more details. 27 * 28 * You should have received a copy of the GNU General Public License 29 * along with this program; if not, write to the Free Software 30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, 31 * MA 02111-1307 USA 32 */ 33 34 #include <linux/kernel.h> 35 #include <linux/of_platform.h> 36 #include <linux/sysfs.h> 37 #include <linux/cpu.h> 38 #include <linux/suspend.h> 39 #include <asm/dcr.h> 40 #include <asm/dcr-native.h> 41 #include <asm/machdep.h> 42 43 #define CPM_ER 0 44 #define CPM_FR 1 45 #define CPM_SR 2 46 47 #define CPM_IDLE_WAIT 0 48 #define CPM_IDLE_DOZE 1 49 50 struct cpm { 51 dcr_host_t dcr_host; 52 unsigned int dcr_offset[3]; 53 unsigned int powersave_off; 54 unsigned int unused; 55 unsigned int idle_doze; 56 unsigned int standby; 57 unsigned int suspend; 58 }; 59 60 static struct cpm cpm; 61 62 struct cpm_idle_mode { 63 unsigned int enabled; 64 const char *name; 65 }; 66 67 static struct cpm_idle_mode idle_mode[] = { 68 [CPM_IDLE_WAIT] = { 1, "wait" }, /* default */ 69 [CPM_IDLE_DOZE] = { 0, "doze" }, 70 }; 71 72 static unsigned int cpm_set(unsigned int cpm_reg, unsigned int mask) 73 { 74 unsigned int value; 75 76 /* CPM controller supports 3 different types of sleep interface 77 * known as class 1, 2 and 3. For class 1 units, they are 78 * unconditionally put to sleep when the corresponding CPM bit is 79 * set. For class 2 and 3 units this is not case; if they can be 80 * put to to sleep, they will. Here we do not verify, we just 81 * set them and expect them to eventually go off when they can. 82 */ 83 value = dcr_read(cpm.dcr_host, cpm.dcr_offset[cpm_reg]); 84 dcr_write(cpm.dcr_host, cpm.dcr_offset[cpm_reg], value | mask); 85 86 /* return old state, to restore later if needed */ 87 return value; 88 } 89 90 static void cpm_idle_wait(void) 91 { 92 unsigned long msr_save; 93 94 /* save off initial state */ 95 msr_save = mfmsr(); 96 /* sync required when CPM0_ER[CPU] is set */ 97 mb(); 98 /* set wait state MSR */ 99 mtmsr(msr_save|MSR_WE|MSR_EE|MSR_CE|MSR_DE); 100 isync(); 101 /* return to initial state */ 102 mtmsr(msr_save); 103 isync(); 104 } 105 106 static void cpm_idle_sleep(unsigned int mask) 107 { 108 unsigned int er_save; 109 110 /* update CPM_ER state */ 111 er_save = cpm_set(CPM_ER, mask); 112 113 /* go to wait state so that CPM0_ER[CPU] can take effect */ 114 cpm_idle_wait(); 115 116 /* restore CPM_ER state */ 117 dcr_write(cpm.dcr_host, cpm.dcr_offset[CPM_ER], er_save); 118 } 119 120 static void cpm_idle_doze(void) 121 { 122 cpm_idle_sleep(cpm.idle_doze); 123 } 124 125 static void cpm_idle_config(int mode) 126 { 127 int i; 128 129 if (idle_mode[mode].enabled) 130 return; 131 132 for (i = 0; i < ARRAY_SIZE(idle_mode); i++) 133 idle_mode[i].enabled = 0; 134 135 idle_mode[mode].enabled = 1; 136 } 137 138 static ssize_t cpm_idle_show(struct kobject *kobj, 139 struct kobj_attribute *attr, char *buf) 140 { 141 char *s = buf; 142 int i; 143 144 for (i = 0; i < ARRAY_SIZE(idle_mode); i++) { 145 if (idle_mode[i].enabled) 146 s += sprintf(s, "[%s] ", idle_mode[i].name); 147 else 148 s += sprintf(s, "%s ", idle_mode[i].name); 149 } 150 151 *(s-1) = '\n'; /* convert the last space to a newline */ 152 153 return s - buf; 154 } 155 156 static ssize_t cpm_idle_store(struct kobject *kobj, 157 struct kobj_attribute *attr, 158 const char *buf, size_t n) 159 { 160 int i; 161 char *p; 162 int len; 163 164 p = memchr(buf, '\n', n); 165 len = p ? p - buf : n; 166 167 for (i = 0; i < ARRAY_SIZE(idle_mode); i++) { 168 if (strncmp(buf, idle_mode[i].name, len) == 0) { 169 cpm_idle_config(i); 170 return n; 171 } 172 } 173 174 return -EINVAL; 175 } 176 177 static struct kobj_attribute cpm_idle_attr = 178 __ATTR(idle, 0644, cpm_idle_show, cpm_idle_store); 179 180 static void cpm_idle_config_sysfs(void) 181 { 182 struct device *dev; 183 unsigned long ret; 184 185 dev = get_cpu_device(0); 186 187 ret = sysfs_create_file(&dev->kobj, 188 &cpm_idle_attr.attr); 189 if (ret) 190 printk(KERN_WARNING 191 "cpm: failed to create idle sysfs entry\n"); 192 } 193 194 static void cpm_idle(void) 195 { 196 if (idle_mode[CPM_IDLE_DOZE].enabled) 197 cpm_idle_doze(); 198 else 199 cpm_idle_wait(); 200 } 201 202 static int cpm_suspend_valid(suspend_state_t state) 203 { 204 switch (state) { 205 case PM_SUSPEND_STANDBY: 206 return !!cpm.standby; 207 case PM_SUSPEND_MEM: 208 return !!cpm.suspend; 209 default: 210 return 0; 211 } 212 } 213 214 static void cpm_suspend_standby(unsigned int mask) 215 { 216 unsigned long tcr_save; 217 218 /* disable decrement interrupt */ 219 tcr_save = mfspr(SPRN_TCR); 220 mtspr(SPRN_TCR, tcr_save & ~TCR_DIE); 221 222 /* go to sleep state */ 223 cpm_idle_sleep(mask); 224 225 /* restore decrement interrupt */ 226 mtspr(SPRN_TCR, tcr_save); 227 } 228 229 static int cpm_suspend_enter(suspend_state_t state) 230 { 231 switch (state) { 232 case PM_SUSPEND_STANDBY: 233 cpm_suspend_standby(cpm.standby); 234 break; 235 case PM_SUSPEND_MEM: 236 cpm_suspend_standby(cpm.suspend); 237 break; 238 } 239 240 return 0; 241 } 242 243 static struct platform_suspend_ops cpm_suspend_ops = { 244 .valid = cpm_suspend_valid, 245 .enter = cpm_suspend_enter, 246 }; 247 248 static int cpm_get_uint_property(struct device_node *np, 249 const char *name) 250 { 251 int len; 252 const unsigned int *prop = of_get_property(np, name, &len); 253 254 if (prop == NULL || len < sizeof(u32)) 255 return 0; 256 257 return *prop; 258 } 259 260 static int __init cpm_init(void) 261 { 262 struct device_node *np; 263 int dcr_base, dcr_len; 264 int ret = 0; 265 266 if (!cpm.powersave_off) { 267 cpm_idle_config(CPM_IDLE_WAIT); 268 ppc_md.power_save = &cpm_idle; 269 } 270 271 np = of_find_compatible_node(NULL, NULL, "ibm,cpm"); 272 if (!np) { 273 ret = -EINVAL; 274 goto out; 275 } 276 277 dcr_base = dcr_resource_start(np, 0); 278 dcr_len = dcr_resource_len(np, 0); 279 280 if (dcr_base == 0 || dcr_len == 0) { 281 printk(KERN_ERR "cpm: could not parse dcr property for %s\n", 282 np->full_name); 283 ret = -EINVAL; 284 goto node_put; 285 } 286 287 cpm.dcr_host = dcr_map(np, dcr_base, dcr_len); 288 289 if (!DCR_MAP_OK(cpm.dcr_host)) { 290 printk(KERN_ERR "cpm: failed to map dcr property for %s\n", 291 np->full_name); 292 ret = -EINVAL; 293 goto node_put; 294 } 295 296 /* All 4xx SoCs with a CPM controller have one of two 297 * different order for the CPM registers. Some have the 298 * CPM registers in the following order (ER,FR,SR). The 299 * others have them in the following order (SR,ER,FR). 300 */ 301 302 if (cpm_get_uint_property(np, "er-offset") == 0) { 303 cpm.dcr_offset[CPM_ER] = 0; 304 cpm.dcr_offset[CPM_FR] = 1; 305 cpm.dcr_offset[CPM_SR] = 2; 306 } else { 307 cpm.dcr_offset[CPM_ER] = 1; 308 cpm.dcr_offset[CPM_FR] = 2; 309 cpm.dcr_offset[CPM_SR] = 0; 310 } 311 312 /* Now let's see what IPs to turn off for the following modes */ 313 314 cpm.unused = cpm_get_uint_property(np, "unused-units"); 315 cpm.idle_doze = cpm_get_uint_property(np, "idle-doze"); 316 cpm.standby = cpm_get_uint_property(np, "standby"); 317 cpm.suspend = cpm_get_uint_property(np, "suspend"); 318 319 /* If some IPs are unused let's turn them off now */ 320 321 if (cpm.unused) { 322 cpm_set(CPM_ER, cpm.unused); 323 cpm_set(CPM_FR, cpm.unused); 324 } 325 326 /* Now let's export interfaces */ 327 328 if (!cpm.powersave_off && cpm.idle_doze) 329 cpm_idle_config_sysfs(); 330 331 if (cpm.standby || cpm.suspend) 332 suspend_set_ops(&cpm_suspend_ops); 333 node_put: 334 of_node_put(np); 335 out: 336 return ret; 337 } 338 339 late_initcall(cpm_init); 340 341 static int __init cpm_powersave_off(char *arg) 342 { 343 cpm.powersave_off = 1; 344 return 0; 345 } 346 __setup("powersave=off", cpm_powersave_off); 347