18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * PowerPC 4xx Clock and Power Management 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2010, Applied Micro Circuits Corporation 68c2ecf20Sopenharmony_ci * Victor Gallardo (vgallardo@apm.com) 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Based on arch/powerpc/platforms/44x/idle.c: 98c2ecf20Sopenharmony_ci * Jerone Young <jyoung5@us.ibm.com> 108c2ecf20Sopenharmony_ci * Copyright 2008 IBM Corp. 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * Based on arch/powerpc/sysdev/fsl_pmc.c: 138c2ecf20Sopenharmony_ci * Anton Vorontsov <avorontsov@ru.mvista.com> 148c2ecf20Sopenharmony_ci * Copyright 2009 MontaVista Software, Inc. 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * See file CREDITS for list of people who contributed to this 178c2ecf20Sopenharmony_ci * project. 188c2ecf20Sopenharmony_ci */ 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include <linux/kernel.h> 218c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 228c2ecf20Sopenharmony_ci#include <linux/sysfs.h> 238c2ecf20Sopenharmony_ci#include <linux/cpu.h> 248c2ecf20Sopenharmony_ci#include <linux/suspend.h> 258c2ecf20Sopenharmony_ci#include <asm/dcr.h> 268c2ecf20Sopenharmony_ci#include <asm/dcr-native.h> 278c2ecf20Sopenharmony_ci#include <asm/machdep.h> 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define CPM_ER 0 308c2ecf20Sopenharmony_ci#define CPM_FR 1 318c2ecf20Sopenharmony_ci#define CPM_SR 2 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#define CPM_IDLE_WAIT 0 348c2ecf20Sopenharmony_ci#define CPM_IDLE_DOZE 1 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistruct cpm { 378c2ecf20Sopenharmony_ci dcr_host_t dcr_host; 388c2ecf20Sopenharmony_ci unsigned int dcr_offset[3]; 398c2ecf20Sopenharmony_ci unsigned int powersave_off; 408c2ecf20Sopenharmony_ci unsigned int unused; 418c2ecf20Sopenharmony_ci unsigned int idle_doze; 428c2ecf20Sopenharmony_ci unsigned int standby; 438c2ecf20Sopenharmony_ci unsigned int suspend; 448c2ecf20Sopenharmony_ci}; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistatic struct cpm cpm; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistruct cpm_idle_mode { 498c2ecf20Sopenharmony_ci unsigned int enabled; 508c2ecf20Sopenharmony_ci const char *name; 518c2ecf20Sopenharmony_ci}; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic struct cpm_idle_mode idle_mode[] = { 548c2ecf20Sopenharmony_ci [CPM_IDLE_WAIT] = { 1, "wait" }, /* default */ 558c2ecf20Sopenharmony_ci [CPM_IDLE_DOZE] = { 0, "doze" }, 568c2ecf20Sopenharmony_ci}; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic unsigned int cpm_set(unsigned int cpm_reg, unsigned int mask) 598c2ecf20Sopenharmony_ci{ 608c2ecf20Sopenharmony_ci unsigned int value; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci /* CPM controller supports 3 different types of sleep interface 638c2ecf20Sopenharmony_ci * known as class 1, 2 and 3. For class 1 units, they are 648c2ecf20Sopenharmony_ci * unconditionally put to sleep when the corresponding CPM bit is 658c2ecf20Sopenharmony_ci * set. For class 2 and 3 units this is not case; if they can be 668c2ecf20Sopenharmony_ci * put to to sleep, they will. Here we do not verify, we just 678c2ecf20Sopenharmony_ci * set them and expect them to eventually go off when they can. 688c2ecf20Sopenharmony_ci */ 698c2ecf20Sopenharmony_ci value = dcr_read(cpm.dcr_host, cpm.dcr_offset[cpm_reg]); 708c2ecf20Sopenharmony_ci dcr_write(cpm.dcr_host, cpm.dcr_offset[cpm_reg], value | mask); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci /* return old state, to restore later if needed */ 738c2ecf20Sopenharmony_ci return value; 748c2ecf20Sopenharmony_ci} 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_cistatic void cpm_idle_wait(void) 778c2ecf20Sopenharmony_ci{ 788c2ecf20Sopenharmony_ci unsigned long msr_save; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci /* save off initial state */ 818c2ecf20Sopenharmony_ci msr_save = mfmsr(); 828c2ecf20Sopenharmony_ci /* sync required when CPM0_ER[CPU] is set */ 838c2ecf20Sopenharmony_ci mb(); 848c2ecf20Sopenharmony_ci /* set wait state MSR */ 858c2ecf20Sopenharmony_ci mtmsr(msr_save|MSR_WE|MSR_EE|MSR_CE|MSR_DE); 868c2ecf20Sopenharmony_ci isync(); 878c2ecf20Sopenharmony_ci /* return to initial state */ 888c2ecf20Sopenharmony_ci mtmsr(msr_save); 898c2ecf20Sopenharmony_ci isync(); 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic void cpm_idle_sleep(unsigned int mask) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci unsigned int er_save; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci /* update CPM_ER state */ 978c2ecf20Sopenharmony_ci er_save = cpm_set(CPM_ER, mask); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci /* go to wait state so that CPM0_ER[CPU] can take effect */ 1008c2ecf20Sopenharmony_ci cpm_idle_wait(); 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci /* restore CPM_ER state */ 1038c2ecf20Sopenharmony_ci dcr_write(cpm.dcr_host, cpm.dcr_offset[CPM_ER], er_save); 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic void cpm_idle_doze(void) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci cpm_idle_sleep(cpm.idle_doze); 1098c2ecf20Sopenharmony_ci} 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_cistatic void cpm_idle_config(int mode) 1128c2ecf20Sopenharmony_ci{ 1138c2ecf20Sopenharmony_ci int i; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci if (idle_mode[mode].enabled) 1168c2ecf20Sopenharmony_ci return; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(idle_mode); i++) 1198c2ecf20Sopenharmony_ci idle_mode[i].enabled = 0; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci idle_mode[mode].enabled = 1; 1228c2ecf20Sopenharmony_ci} 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_cistatic ssize_t cpm_idle_show(struct kobject *kobj, 1258c2ecf20Sopenharmony_ci struct kobj_attribute *attr, char *buf) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci char *s = buf; 1288c2ecf20Sopenharmony_ci int i; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(idle_mode); i++) { 1318c2ecf20Sopenharmony_ci if (idle_mode[i].enabled) 1328c2ecf20Sopenharmony_ci s += sprintf(s, "[%s] ", idle_mode[i].name); 1338c2ecf20Sopenharmony_ci else 1348c2ecf20Sopenharmony_ci s += sprintf(s, "%s ", idle_mode[i].name); 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci *(s-1) = '\n'; /* convert the last space to a newline */ 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci return s - buf; 1408c2ecf20Sopenharmony_ci} 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_cistatic ssize_t cpm_idle_store(struct kobject *kobj, 1438c2ecf20Sopenharmony_ci struct kobj_attribute *attr, 1448c2ecf20Sopenharmony_ci const char *buf, size_t n) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci int i; 1478c2ecf20Sopenharmony_ci char *p; 1488c2ecf20Sopenharmony_ci int len; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci p = memchr(buf, '\n', n); 1518c2ecf20Sopenharmony_ci len = p ? p - buf : n; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(idle_mode); i++) { 1548c2ecf20Sopenharmony_ci if (strncmp(buf, idle_mode[i].name, len) == 0) { 1558c2ecf20Sopenharmony_ci cpm_idle_config(i); 1568c2ecf20Sopenharmony_ci return n; 1578c2ecf20Sopenharmony_ci } 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci return -EINVAL; 1618c2ecf20Sopenharmony_ci} 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_cistatic struct kobj_attribute cpm_idle_attr = 1648c2ecf20Sopenharmony_ci __ATTR(idle, 0644, cpm_idle_show, cpm_idle_store); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic void cpm_idle_config_sysfs(void) 1678c2ecf20Sopenharmony_ci{ 1688c2ecf20Sopenharmony_ci struct device *dev; 1698c2ecf20Sopenharmony_ci unsigned long ret; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci dev = get_cpu_device(0); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci ret = sysfs_create_file(&dev->kobj, 1748c2ecf20Sopenharmony_ci &cpm_idle_attr.attr); 1758c2ecf20Sopenharmony_ci if (ret) 1768c2ecf20Sopenharmony_ci printk(KERN_WARNING 1778c2ecf20Sopenharmony_ci "cpm: failed to create idle sysfs entry\n"); 1788c2ecf20Sopenharmony_ci} 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_cistatic void cpm_idle(void) 1818c2ecf20Sopenharmony_ci{ 1828c2ecf20Sopenharmony_ci if (idle_mode[CPM_IDLE_DOZE].enabled) 1838c2ecf20Sopenharmony_ci cpm_idle_doze(); 1848c2ecf20Sopenharmony_ci else 1858c2ecf20Sopenharmony_ci cpm_idle_wait(); 1868c2ecf20Sopenharmony_ci} 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_cistatic int cpm_suspend_valid(suspend_state_t state) 1898c2ecf20Sopenharmony_ci{ 1908c2ecf20Sopenharmony_ci switch (state) { 1918c2ecf20Sopenharmony_ci case PM_SUSPEND_STANDBY: 1928c2ecf20Sopenharmony_ci return !!cpm.standby; 1938c2ecf20Sopenharmony_ci case PM_SUSPEND_MEM: 1948c2ecf20Sopenharmony_ci return !!cpm.suspend; 1958c2ecf20Sopenharmony_ci default: 1968c2ecf20Sopenharmony_ci return 0; 1978c2ecf20Sopenharmony_ci } 1988c2ecf20Sopenharmony_ci} 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_cistatic void cpm_suspend_standby(unsigned int mask) 2018c2ecf20Sopenharmony_ci{ 2028c2ecf20Sopenharmony_ci unsigned long tcr_save; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci /* disable decrement interrupt */ 2058c2ecf20Sopenharmony_ci tcr_save = mfspr(SPRN_TCR); 2068c2ecf20Sopenharmony_ci mtspr(SPRN_TCR, tcr_save & ~TCR_DIE); 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci /* go to sleep state */ 2098c2ecf20Sopenharmony_ci cpm_idle_sleep(mask); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci /* restore decrement interrupt */ 2128c2ecf20Sopenharmony_ci mtspr(SPRN_TCR, tcr_save); 2138c2ecf20Sopenharmony_ci} 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_cistatic int cpm_suspend_enter(suspend_state_t state) 2168c2ecf20Sopenharmony_ci{ 2178c2ecf20Sopenharmony_ci switch (state) { 2188c2ecf20Sopenharmony_ci case PM_SUSPEND_STANDBY: 2198c2ecf20Sopenharmony_ci cpm_suspend_standby(cpm.standby); 2208c2ecf20Sopenharmony_ci break; 2218c2ecf20Sopenharmony_ci case PM_SUSPEND_MEM: 2228c2ecf20Sopenharmony_ci cpm_suspend_standby(cpm.suspend); 2238c2ecf20Sopenharmony_ci break; 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci return 0; 2278c2ecf20Sopenharmony_ci} 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_cistatic const struct platform_suspend_ops cpm_suspend_ops = { 2308c2ecf20Sopenharmony_ci .valid = cpm_suspend_valid, 2318c2ecf20Sopenharmony_ci .enter = cpm_suspend_enter, 2328c2ecf20Sopenharmony_ci}; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_cistatic int cpm_get_uint_property(struct device_node *np, 2358c2ecf20Sopenharmony_ci const char *name) 2368c2ecf20Sopenharmony_ci{ 2378c2ecf20Sopenharmony_ci int len; 2388c2ecf20Sopenharmony_ci const unsigned int *prop = of_get_property(np, name, &len); 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci if (prop == NULL || len < sizeof(u32)) 2418c2ecf20Sopenharmony_ci return 0; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci return *prop; 2448c2ecf20Sopenharmony_ci} 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_cistatic int __init cpm_init(void) 2478c2ecf20Sopenharmony_ci{ 2488c2ecf20Sopenharmony_ci struct device_node *np; 2498c2ecf20Sopenharmony_ci int dcr_base, dcr_len; 2508c2ecf20Sopenharmony_ci int ret = 0; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci if (!cpm.powersave_off) { 2538c2ecf20Sopenharmony_ci cpm_idle_config(CPM_IDLE_WAIT); 2548c2ecf20Sopenharmony_ci ppc_md.power_save = &cpm_idle; 2558c2ecf20Sopenharmony_ci } 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, "ibm,cpm"); 2588c2ecf20Sopenharmony_ci if (!np) { 2598c2ecf20Sopenharmony_ci ret = -EINVAL; 2608c2ecf20Sopenharmony_ci goto out; 2618c2ecf20Sopenharmony_ci } 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci dcr_base = dcr_resource_start(np, 0); 2648c2ecf20Sopenharmony_ci dcr_len = dcr_resource_len(np, 0); 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci if (dcr_base == 0 || dcr_len == 0) { 2678c2ecf20Sopenharmony_ci printk(KERN_ERR "cpm: could not parse dcr property for %pOF\n", 2688c2ecf20Sopenharmony_ci np); 2698c2ecf20Sopenharmony_ci ret = -EINVAL; 2708c2ecf20Sopenharmony_ci goto node_put; 2718c2ecf20Sopenharmony_ci } 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci cpm.dcr_host = dcr_map(np, dcr_base, dcr_len); 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci if (!DCR_MAP_OK(cpm.dcr_host)) { 2768c2ecf20Sopenharmony_ci printk(KERN_ERR "cpm: failed to map dcr property for %pOF\n", 2778c2ecf20Sopenharmony_ci np); 2788c2ecf20Sopenharmony_ci ret = -EINVAL; 2798c2ecf20Sopenharmony_ci goto node_put; 2808c2ecf20Sopenharmony_ci } 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci /* All 4xx SoCs with a CPM controller have one of two 2838c2ecf20Sopenharmony_ci * different order for the CPM registers. Some have the 2848c2ecf20Sopenharmony_ci * CPM registers in the following order (ER,FR,SR). The 2858c2ecf20Sopenharmony_ci * others have them in the following order (SR,ER,FR). 2868c2ecf20Sopenharmony_ci */ 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci if (cpm_get_uint_property(np, "er-offset") == 0) { 2898c2ecf20Sopenharmony_ci cpm.dcr_offset[CPM_ER] = 0; 2908c2ecf20Sopenharmony_ci cpm.dcr_offset[CPM_FR] = 1; 2918c2ecf20Sopenharmony_ci cpm.dcr_offset[CPM_SR] = 2; 2928c2ecf20Sopenharmony_ci } else { 2938c2ecf20Sopenharmony_ci cpm.dcr_offset[CPM_ER] = 1; 2948c2ecf20Sopenharmony_ci cpm.dcr_offset[CPM_FR] = 2; 2958c2ecf20Sopenharmony_ci cpm.dcr_offset[CPM_SR] = 0; 2968c2ecf20Sopenharmony_ci } 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci /* Now let's see what IPs to turn off for the following modes */ 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci cpm.unused = cpm_get_uint_property(np, "unused-units"); 3018c2ecf20Sopenharmony_ci cpm.idle_doze = cpm_get_uint_property(np, "idle-doze"); 3028c2ecf20Sopenharmony_ci cpm.standby = cpm_get_uint_property(np, "standby"); 3038c2ecf20Sopenharmony_ci cpm.suspend = cpm_get_uint_property(np, "suspend"); 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ci /* If some IPs are unused let's turn them off now */ 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci if (cpm.unused) { 3088c2ecf20Sopenharmony_ci cpm_set(CPM_ER, cpm.unused); 3098c2ecf20Sopenharmony_ci cpm_set(CPM_FR, cpm.unused); 3108c2ecf20Sopenharmony_ci } 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ci /* Now let's export interfaces */ 3138c2ecf20Sopenharmony_ci 3148c2ecf20Sopenharmony_ci if (!cpm.powersave_off && cpm.idle_doze) 3158c2ecf20Sopenharmony_ci cpm_idle_config_sysfs(); 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci if (cpm.standby || cpm.suspend) 3188c2ecf20Sopenharmony_ci suspend_set_ops(&cpm_suspend_ops); 3198c2ecf20Sopenharmony_cinode_put: 3208c2ecf20Sopenharmony_ci of_node_put(np); 3218c2ecf20Sopenharmony_ciout: 3228c2ecf20Sopenharmony_ci return ret; 3238c2ecf20Sopenharmony_ci} 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_cilate_initcall(cpm_init); 3268c2ecf20Sopenharmony_ci 3278c2ecf20Sopenharmony_cistatic int __init cpm_powersave_off(char *arg) 3288c2ecf20Sopenharmony_ci{ 3298c2ecf20Sopenharmony_ci cpm.powersave_off = 1; 3308c2ecf20Sopenharmony_ci return 1; 3318c2ecf20Sopenharmony_ci} 3328c2ecf20Sopenharmony_ci__setup("powersave=off", cpm_powersave_off); 333