162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PowerPC 4xx Clock and Power Management 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2010, Applied Micro Circuits Corporation 662306a36Sopenharmony_ci * Victor Gallardo (vgallardo@apm.com) 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Based on arch/powerpc/platforms/44x/idle.c: 962306a36Sopenharmony_ci * Jerone Young <jyoung5@us.ibm.com> 1062306a36Sopenharmony_ci * Copyright 2008 IBM Corp. 1162306a36Sopenharmony_ci * 1262306a36Sopenharmony_ci * Based on arch/powerpc/sysdev/fsl_pmc.c: 1362306a36Sopenharmony_ci * Anton Vorontsov <avorontsov@ru.mvista.com> 1462306a36Sopenharmony_ci * Copyright 2009 MontaVista Software, Inc. 1562306a36Sopenharmony_ci * 1662306a36Sopenharmony_ci * See file CREDITS for list of people who contributed to this 1762306a36Sopenharmony_ci * project. 1862306a36Sopenharmony_ci */ 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include <linux/kernel.h> 2162306a36Sopenharmony_ci#include <linux/of.h> 2262306a36Sopenharmony_ci#include <linux/sysfs.h> 2362306a36Sopenharmony_ci#include <linux/cpu.h> 2462306a36Sopenharmony_ci#include <linux/suspend.h> 2562306a36Sopenharmony_ci#include <asm/dcr.h> 2662306a36Sopenharmony_ci#include <asm/dcr-native.h> 2762306a36Sopenharmony_ci#include <asm/machdep.h> 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#define CPM_ER 0 3062306a36Sopenharmony_ci#define CPM_FR 1 3162306a36Sopenharmony_ci#define CPM_SR 2 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#define CPM_IDLE_WAIT 0 3462306a36Sopenharmony_ci#define CPM_IDLE_DOZE 1 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistruct cpm { 3762306a36Sopenharmony_ci dcr_host_t dcr_host; 3862306a36Sopenharmony_ci unsigned int dcr_offset[3]; 3962306a36Sopenharmony_ci unsigned int powersave_off; 4062306a36Sopenharmony_ci unsigned int unused; 4162306a36Sopenharmony_ci unsigned int idle_doze; 4262306a36Sopenharmony_ci unsigned int standby; 4362306a36Sopenharmony_ci unsigned int suspend; 4462306a36Sopenharmony_ci}; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic struct cpm cpm; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistruct cpm_idle_mode { 4962306a36Sopenharmony_ci unsigned int enabled; 5062306a36Sopenharmony_ci const char *name; 5162306a36Sopenharmony_ci}; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic struct cpm_idle_mode idle_mode[] = { 5462306a36Sopenharmony_ci [CPM_IDLE_WAIT] = { 1, "wait" }, /* default */ 5562306a36Sopenharmony_ci [CPM_IDLE_DOZE] = { 0, "doze" }, 5662306a36Sopenharmony_ci}; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic unsigned int cpm_set(unsigned int cpm_reg, unsigned int mask) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci unsigned int value; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci /* CPM controller supports 3 different types of sleep interface 6362306a36Sopenharmony_ci * known as class 1, 2 and 3. For class 1 units, they are 6462306a36Sopenharmony_ci * unconditionally put to sleep when the corresponding CPM bit is 6562306a36Sopenharmony_ci * set. For class 2 and 3 units this is not case; if they can be 6662306a36Sopenharmony_ci * put to sleep, they will. Here we do not verify, we just 6762306a36Sopenharmony_ci * set them and expect them to eventually go off when they can. 6862306a36Sopenharmony_ci */ 6962306a36Sopenharmony_ci value = dcr_read(cpm.dcr_host, cpm.dcr_offset[cpm_reg]); 7062306a36Sopenharmony_ci dcr_write(cpm.dcr_host, cpm.dcr_offset[cpm_reg], value | mask); 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci /* return old state, to restore later if needed */ 7362306a36Sopenharmony_ci return value; 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_cistatic void cpm_idle_wait(void) 7762306a36Sopenharmony_ci{ 7862306a36Sopenharmony_ci unsigned long msr_save; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci /* save off initial state */ 8162306a36Sopenharmony_ci msr_save = mfmsr(); 8262306a36Sopenharmony_ci /* sync required when CPM0_ER[CPU] is set */ 8362306a36Sopenharmony_ci mb(); 8462306a36Sopenharmony_ci /* set wait state MSR */ 8562306a36Sopenharmony_ci mtmsr(msr_save|MSR_WE|MSR_EE|MSR_CE|MSR_DE); 8662306a36Sopenharmony_ci isync(); 8762306a36Sopenharmony_ci /* return to initial state */ 8862306a36Sopenharmony_ci mtmsr(msr_save); 8962306a36Sopenharmony_ci isync(); 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistatic void cpm_idle_sleep(unsigned int mask) 9362306a36Sopenharmony_ci{ 9462306a36Sopenharmony_ci unsigned int er_save; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci /* update CPM_ER state */ 9762306a36Sopenharmony_ci er_save = cpm_set(CPM_ER, mask); 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci /* go to wait state so that CPM0_ER[CPU] can take effect */ 10062306a36Sopenharmony_ci cpm_idle_wait(); 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci /* restore CPM_ER state */ 10362306a36Sopenharmony_ci dcr_write(cpm.dcr_host, cpm.dcr_offset[CPM_ER], er_save); 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic void cpm_idle_doze(void) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci cpm_idle_sleep(cpm.idle_doze); 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cistatic void cpm_idle_config(int mode) 11262306a36Sopenharmony_ci{ 11362306a36Sopenharmony_ci int i; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci if (idle_mode[mode].enabled) 11662306a36Sopenharmony_ci return; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(idle_mode); i++) 11962306a36Sopenharmony_ci idle_mode[i].enabled = 0; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci idle_mode[mode].enabled = 1; 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistatic ssize_t cpm_idle_show(struct kobject *kobj, 12562306a36Sopenharmony_ci struct kobj_attribute *attr, char *buf) 12662306a36Sopenharmony_ci{ 12762306a36Sopenharmony_ci char *s = buf; 12862306a36Sopenharmony_ci int i; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(idle_mode); i++) { 13162306a36Sopenharmony_ci if (idle_mode[i].enabled) 13262306a36Sopenharmony_ci s += sprintf(s, "[%s] ", idle_mode[i].name); 13362306a36Sopenharmony_ci else 13462306a36Sopenharmony_ci s += sprintf(s, "%s ", idle_mode[i].name); 13562306a36Sopenharmony_ci } 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci *(s-1) = '\n'; /* convert the last space to a newline */ 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci return s - buf; 14062306a36Sopenharmony_ci} 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cistatic ssize_t cpm_idle_store(struct kobject *kobj, 14362306a36Sopenharmony_ci struct kobj_attribute *attr, 14462306a36Sopenharmony_ci const char *buf, size_t n) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci int i; 14762306a36Sopenharmony_ci char *p; 14862306a36Sopenharmony_ci int len; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci p = memchr(buf, '\n', n); 15162306a36Sopenharmony_ci len = p ? p - buf : n; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(idle_mode); i++) { 15462306a36Sopenharmony_ci if (strncmp(buf, idle_mode[i].name, len) == 0) { 15562306a36Sopenharmony_ci cpm_idle_config(i); 15662306a36Sopenharmony_ci return n; 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci return -EINVAL; 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_cistatic struct kobj_attribute cpm_idle_attr = 16462306a36Sopenharmony_ci __ATTR(idle, 0644, cpm_idle_show, cpm_idle_store); 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic void __init cpm_idle_config_sysfs(void) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct device *dev; 16962306a36Sopenharmony_ci unsigned long ret; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci dev = get_cpu_device(0); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci ret = sysfs_create_file(&dev->kobj, 17462306a36Sopenharmony_ci &cpm_idle_attr.attr); 17562306a36Sopenharmony_ci if (ret) 17662306a36Sopenharmony_ci printk(KERN_WARNING 17762306a36Sopenharmony_ci "cpm: failed to create idle sysfs entry\n"); 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_cistatic void cpm_idle(void) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci if (idle_mode[CPM_IDLE_DOZE].enabled) 18362306a36Sopenharmony_ci cpm_idle_doze(); 18462306a36Sopenharmony_ci else 18562306a36Sopenharmony_ci cpm_idle_wait(); 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_cistatic int cpm_suspend_valid(suspend_state_t state) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci switch (state) { 19162306a36Sopenharmony_ci case PM_SUSPEND_STANDBY: 19262306a36Sopenharmony_ci return !!cpm.standby; 19362306a36Sopenharmony_ci case PM_SUSPEND_MEM: 19462306a36Sopenharmony_ci return !!cpm.suspend; 19562306a36Sopenharmony_ci default: 19662306a36Sopenharmony_ci return 0; 19762306a36Sopenharmony_ci } 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic void cpm_suspend_standby(unsigned int mask) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci unsigned long tcr_save; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci /* disable decrement interrupt */ 20562306a36Sopenharmony_ci tcr_save = mfspr(SPRN_TCR); 20662306a36Sopenharmony_ci mtspr(SPRN_TCR, tcr_save & ~TCR_DIE); 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci /* go to sleep state */ 20962306a36Sopenharmony_ci cpm_idle_sleep(mask); 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci /* restore decrement interrupt */ 21262306a36Sopenharmony_ci mtspr(SPRN_TCR, tcr_save); 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_cistatic int cpm_suspend_enter(suspend_state_t state) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci switch (state) { 21862306a36Sopenharmony_ci case PM_SUSPEND_STANDBY: 21962306a36Sopenharmony_ci cpm_suspend_standby(cpm.standby); 22062306a36Sopenharmony_ci break; 22162306a36Sopenharmony_ci case PM_SUSPEND_MEM: 22262306a36Sopenharmony_ci cpm_suspend_standby(cpm.suspend); 22362306a36Sopenharmony_ci break; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci return 0; 22762306a36Sopenharmony_ci} 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_cistatic const struct platform_suspend_ops cpm_suspend_ops = { 23062306a36Sopenharmony_ci .valid = cpm_suspend_valid, 23162306a36Sopenharmony_ci .enter = cpm_suspend_enter, 23262306a36Sopenharmony_ci}; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_cistatic int __init cpm_get_uint_property(struct device_node *np, 23562306a36Sopenharmony_ci const char *name) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci int len; 23862306a36Sopenharmony_ci const unsigned int *prop = of_get_property(np, name, &len); 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci if (prop == NULL || len < sizeof(u32)) 24162306a36Sopenharmony_ci return 0; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci return *prop; 24462306a36Sopenharmony_ci} 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_cistatic int __init cpm_init(void) 24762306a36Sopenharmony_ci{ 24862306a36Sopenharmony_ci struct device_node *np; 24962306a36Sopenharmony_ci int dcr_base, dcr_len; 25062306a36Sopenharmony_ci int ret = 0; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci if (!cpm.powersave_off) { 25362306a36Sopenharmony_ci cpm_idle_config(CPM_IDLE_WAIT); 25462306a36Sopenharmony_ci ppc_md.power_save = &cpm_idle; 25562306a36Sopenharmony_ci } 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci np = of_find_compatible_node(NULL, NULL, "ibm,cpm"); 25862306a36Sopenharmony_ci if (!np) { 25962306a36Sopenharmony_ci ret = -EINVAL; 26062306a36Sopenharmony_ci goto out; 26162306a36Sopenharmony_ci } 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci dcr_base = dcr_resource_start(np, 0); 26462306a36Sopenharmony_ci dcr_len = dcr_resource_len(np, 0); 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci if (dcr_base == 0 || dcr_len == 0) { 26762306a36Sopenharmony_ci printk(KERN_ERR "cpm: could not parse dcr property for %pOF\n", 26862306a36Sopenharmony_ci np); 26962306a36Sopenharmony_ci ret = -EINVAL; 27062306a36Sopenharmony_ci goto node_put; 27162306a36Sopenharmony_ci } 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci cpm.dcr_host = dcr_map(np, dcr_base, dcr_len); 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci if (!DCR_MAP_OK(cpm.dcr_host)) { 27662306a36Sopenharmony_ci printk(KERN_ERR "cpm: failed to map dcr property for %pOF\n", 27762306a36Sopenharmony_ci np); 27862306a36Sopenharmony_ci ret = -EINVAL; 27962306a36Sopenharmony_ci goto node_put; 28062306a36Sopenharmony_ci } 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci /* All 4xx SoCs with a CPM controller have one of two 28362306a36Sopenharmony_ci * different order for the CPM registers. Some have the 28462306a36Sopenharmony_ci * CPM registers in the following order (ER,FR,SR). The 28562306a36Sopenharmony_ci * others have them in the following order (SR,ER,FR). 28662306a36Sopenharmony_ci */ 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci if (cpm_get_uint_property(np, "er-offset") == 0) { 28962306a36Sopenharmony_ci cpm.dcr_offset[CPM_ER] = 0; 29062306a36Sopenharmony_ci cpm.dcr_offset[CPM_FR] = 1; 29162306a36Sopenharmony_ci cpm.dcr_offset[CPM_SR] = 2; 29262306a36Sopenharmony_ci } else { 29362306a36Sopenharmony_ci cpm.dcr_offset[CPM_ER] = 1; 29462306a36Sopenharmony_ci cpm.dcr_offset[CPM_FR] = 2; 29562306a36Sopenharmony_ci cpm.dcr_offset[CPM_SR] = 0; 29662306a36Sopenharmony_ci } 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci /* Now let's see what IPs to turn off for the following modes */ 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci cpm.unused = cpm_get_uint_property(np, "unused-units"); 30162306a36Sopenharmony_ci cpm.idle_doze = cpm_get_uint_property(np, "idle-doze"); 30262306a36Sopenharmony_ci cpm.standby = cpm_get_uint_property(np, "standby"); 30362306a36Sopenharmony_ci cpm.suspend = cpm_get_uint_property(np, "suspend"); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci /* If some IPs are unused let's turn them off now */ 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci if (cpm.unused) { 30862306a36Sopenharmony_ci cpm_set(CPM_ER, cpm.unused); 30962306a36Sopenharmony_ci cpm_set(CPM_FR, cpm.unused); 31062306a36Sopenharmony_ci } 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci /* Now let's export interfaces */ 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci if (!cpm.powersave_off && cpm.idle_doze) 31562306a36Sopenharmony_ci cpm_idle_config_sysfs(); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci if (cpm.standby || cpm.suspend) 31862306a36Sopenharmony_ci suspend_set_ops(&cpm_suspend_ops); 31962306a36Sopenharmony_cinode_put: 32062306a36Sopenharmony_ci of_node_put(np); 32162306a36Sopenharmony_ciout: 32262306a36Sopenharmony_ci return ret; 32362306a36Sopenharmony_ci} 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_cilate_initcall(cpm_init); 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_cistatic int __init cpm_powersave_off(char *arg) 32862306a36Sopenharmony_ci{ 32962306a36Sopenharmony_ci cpm.powersave_off = 1; 33062306a36Sopenharmony_ci return 1; 33162306a36Sopenharmony_ci} 33262306a36Sopenharmony_ci__setup("powersave=off", cpm_powersave_off); 333