/* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #define GET_CPU_OF_ATTR(attr) \ (container_of(attr, struct msm_pm_kobj_attribute, ka)->cpu) struct msm_pm_platform_data { u8 idle_supported; /* Allow device to enter mode during idle */ u8 suspend_supported; /* Allow device to enter mode during suspend */ u8 suspend_enabled; /* enabled for suspend */ u8 idle_enabled; /* enabled for idle low power */ u32 latency; /* interrupt latency in microseconds when entering and exiting the low power mode */ u32 residency; /* time threshold in microseconds beyond which staying in the low power mode saves power */ }; static struct msm_pm_platform_data msm_pm_sleep_modes[] = { [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND)] = { .idle_supported = 0, .suspend_supported = 1, .idle_enabled = 0, .suspend_enabled = 1, }, [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { .idle_supported = 1, .suspend_supported = 1, .idle_enabled = 0, .suspend_enabled = 0, }, [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE)] = { .idle_supported = 1, .suspend_supported = 1, .idle_enabled = 0, .suspend_enabled = 0, }, [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_RETENTION)] = { .idle_supported = 1, .suspend_supported = 0, .idle_enabled = 0, .suspend_enabled = 0, }, [MSM_PM_MODE(0, MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT)] = { .idle_supported = 1, .suspend_supported = 1, .idle_enabled = 1, .suspend_enabled = 1, }, [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { .idle_supported = 1, .suspend_supported = 1, .idle_enabled = 0, .suspend_enabled = 1, }, [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND)] = { .idle_supported = 0, .suspend_supported = 0, .idle_enabled = 0, .suspend_enabled = 0, }, [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE)] = { .idle_supported = 1, .suspend_supported = 1, .idle_enabled = 0, .suspend_enabled = 0, }, [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_RETENTION)] = { .idle_supported = 1, .suspend_supported = 1, .idle_enabled = 0, .suspend_enabled = 0, }, [MSM_PM_MODE(1, MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT)] = { .idle_supported = 1, .suspend_supported = 0, .idle_enabled = 1, .suspend_enabled = 0, }, [MSM_PM_MODE(2, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND)] = { .idle_supported = 0, .suspend_supported = 0, .idle_enabled = 0, .suspend_enabled = 0, }, [MSM_PM_MODE(2, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { .idle_supported = 1, .suspend_supported = 1, .idle_enabled = 0, .suspend_enabled = 1, }, [MSM_PM_MODE(2, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE)] = { .idle_supported = 1, .suspend_supported = 1, .idle_enabled = 0, .suspend_enabled = 0, }, [MSM_PM_MODE(2, MSM_PM_SLEEP_MODE_RETENTION)] = { .idle_supported = 1, .suspend_supported = 1, .idle_enabled = 0, .suspend_enabled = 0, }, [MSM_PM_MODE(2, MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT)] = { .idle_supported = 1, .suspend_supported = 0, .idle_enabled = 1, .suspend_enabled = 0, }, [MSM_PM_MODE(3, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND)] = { .idle_supported = 0, .suspend_supported = 0, .idle_enabled = 0, .suspend_enabled = 0, }, [MSM_PM_MODE(3, MSM_PM_SLEEP_MODE_POWER_COLLAPSE)] = { .idle_supported = 1, .suspend_supported = 1, .idle_enabled = 0, .suspend_enabled = 1, }, [MSM_PM_MODE(3, MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE)] = { .idle_supported = 1, .suspend_supported = 1, .idle_enabled = 0, .suspend_enabled = 0, }, [MSM_PM_MODE(3, MSM_PM_SLEEP_MODE_RETENTION)] = { .idle_supported = 1, .suspend_supported = 1, .idle_enabled = 0, .suspend_enabled = 0, }, [MSM_PM_MODE(3, MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT)] = { .idle_supported = 1, .suspend_supported = 0, .idle_enabled = 1, .suspend_enabled = 0, }, [MSM_PM_MODE(3, MSM_PM_SLEEP_MODE_NR)] = { .idle_supported = 0, .suspend_supported = 0, .idle_enabled = 0, .suspend_enabled = 0, }, }; enum { MSM_PM_MODE_ATTR_SUSPEND, MSM_PM_MODE_ATTR_IDLE, MSM_PM_MODE_ATTR_NR, }; static char *msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_NR] = { [MSM_PM_MODE_ATTR_SUSPEND] = "suspend_enabled", [MSM_PM_MODE_ATTR_IDLE] = "idle_enabled", }; struct msm_pm_kobj_attribute { unsigned int cpu; struct kobj_attribute ka; }; struct msm_pm_sysfs_sleep_mode { struct kobject *kobj; struct attribute_group attr_group; struct attribute *attrs[MSM_PM_MODE_ATTR_NR + 1]; struct msm_pm_kobj_attribute kas[MSM_PM_MODE_ATTR_NR]; }; static char *msm_pm_sleep_mode_labels[MSM_PM_SLEEP_MODE_NR] = { [MSM_PM_SLEEP_MODE_POWER_COLLAPSE] = "power_collapse", [MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT] = "wfi", [MSM_PM_SLEEP_MODE_RETENTION] = "retention", [MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE] = "standalone_power_collapse", [MSM_PM_SLEEP_MODE_FASTPC] = "fpc", }; /* * Write out the attribute. */ static ssize_t msm_pm_mode_attr_show( struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int ret = -EINVAL; int i; for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) { struct kernel_param kp; unsigned int cpu; struct msm_pm_platform_data *mode; if (msm_pm_sleep_mode_labels[i] == NULL) continue; if (strcmp(kobj->name, msm_pm_sleep_mode_labels[i])) continue; cpu = GET_CPU_OF_ATTR(attr); mode = &msm_pm_sleep_modes[MSM_PM_MODE(cpu, i)]; if (!strcmp(attr->attr.name, msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_SUSPEND])) { u32 arg = mode->suspend_enabled; kp.arg = &arg; ret = param_get_int(buf, &kp); } else if (!strcmp(attr->attr.name, msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_IDLE])) { u32 arg = mode->idle_enabled; kp.arg = &arg; ret = param_get_int(buf, &kp); } break; } if (ret > 0) { strlcat(buf, "\n", PAGE_SIZE); ret++; } return ret; } static ssize_t msm_pm_mode_attr_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int ret = -EINVAL; int i; for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) { struct kernel_param kp; unsigned int cpu; struct msm_pm_platform_data *mode; if (msm_pm_sleep_mode_labels[i] == NULL) continue; if (strcmp(kobj->name, msm_pm_sleep_mode_labels[i])) continue; cpu = GET_CPU_OF_ATTR(attr); mode = &msm_pm_sleep_modes[MSM_PM_MODE(cpu, i)]; if (!strcmp(attr->attr.name, msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_SUSPEND])) { kp.arg = &mode->suspend_enabled; ret = param_set_byte(buf, &kp); } else if (!strcmp(attr->attr.name, msm_pm_mode_attr_labels[MSM_PM_MODE_ATTR_IDLE])) { kp.arg = &mode->idle_enabled; ret = param_set_byte(buf, &kp); } break; } return ret ? ret : count; } static int msm_pm_mode_sysfs_add_cpu( unsigned int cpu, struct kobject *modes_kobj) { char cpu_name[8]; struct kobject *cpu_kobj; struct msm_pm_sysfs_sleep_mode *mode = NULL; int i, j, k; int ret; snprintf(cpu_name, sizeof(cpu_name), "cpu%u", cpu); cpu_kobj = kobject_create_and_add(cpu_name, modes_kobj); if (!cpu_kobj) { pr_err("%s: cannot create %s kobject\n", __func__, cpu_name); ret = -ENOMEM; goto mode_sysfs_add_cpu_exit; } for (i = 0; i < MSM_PM_SLEEP_MODE_NR; i++) { int idx = MSM_PM_MODE(cpu, i); if ((!msm_pm_sleep_modes[idx].suspend_supported) && (!msm_pm_sleep_modes[idx].idle_supported)) continue; if (!msm_pm_sleep_mode_labels[i] || !msm_pm_sleep_mode_labels[i][0]) continue; mode = kzalloc(sizeof(*mode), GFP_KERNEL); if (!mode) { pr_err("%s: cannot allocate memory for attributes\n", __func__); ret = -ENOMEM; goto mode_sysfs_add_cpu_exit; } mode->kobj = kobject_create_and_add( msm_pm_sleep_mode_labels[i], cpu_kobj); if (!mode->kobj) { pr_err("%s: cannot create kobject\n", __func__); ret = -ENOMEM; goto mode_sysfs_add_cpu_exit; } for (k = 0, j = 0; k < MSM_PM_MODE_ATTR_NR; k++) { if ((k == MSM_PM_MODE_ATTR_IDLE) && !msm_pm_sleep_modes[idx].idle_supported) continue; if ((k == MSM_PM_MODE_ATTR_SUSPEND) && !msm_pm_sleep_modes[idx].suspend_supported) continue; sysfs_attr_init(&mode->kas[j].ka.attr); mode->kas[j].cpu = cpu; mode->kas[j].ka.attr.mode = 0644; mode->kas[j].ka.show = msm_pm_mode_attr_show; mode->kas[j].ka.store = msm_pm_mode_attr_store; mode->kas[j].ka.attr.name = msm_pm_mode_attr_labels[k]; mode->attrs[j] = &mode->kas[j].ka.attr; j++; } mode->attrs[j] = NULL; mode->attr_group.attrs = mode->attrs; ret = sysfs_create_group(mode->kobj, &mode->attr_group); if (ret) { pr_err("%s: cannot create kobject attribute group\n", __func__); goto mode_sysfs_add_cpu_exit; } } ret = 0; mode_sysfs_add_cpu_exit: if (ret) { if (mode && mode->kobj) kobject_del(mode->kobj); kfree(mode); } return ret; } int msm_pm_mode_sysfs_add(const char *pm_modname) { struct kobject *module_kobj; struct kobject *modes_kobj; unsigned int cpu; int ret; module_kobj = kset_find_obj(module_kset, pm_modname); if (!module_kobj) { pr_err("%s: cannot find kobject for module %s\n", __func__, pm_modname); ret = -ENOENT; goto mode_sysfs_add_exit; } modes_kobj = kobject_create_and_add("modes", module_kobj); if (!modes_kobj) { pr_err("%s: cannot create modes kobject\n", __func__); ret = -ENOMEM; goto mode_sysfs_add_exit; } for_each_possible_cpu(cpu) { ret = msm_pm_mode_sysfs_add_cpu(cpu, modes_kobj); if (ret) goto mode_sysfs_add_exit; } ret = 0; mode_sysfs_add_exit: return ret; } int msm_pm_sleep_mode_supported(unsigned int cpu, unsigned int mode, bool idle) { int idx = MSM_PM_MODE(cpu, mode); if (idle) return msm_pm_sleep_modes[idx].idle_supported; else return msm_pm_sleep_modes[idx].suspend_supported; } EXPORT_SYMBOL(msm_pm_sleep_mode_supported); int msm_pm_sleep_mode_allow(unsigned int cpu, unsigned int mode, bool idle) { int idx = MSM_PM_MODE(cpu, mode); if ((mode == MSM_PM_SLEEP_MODE_RETENTION) && !msm_pm_retention_enabled()) return false; if (idle) return msm_pm_sleep_modes[idx].idle_enabled && msm_pm_sleep_modes[idx].idle_supported; else return msm_pm_sleep_modes[idx].suspend_enabled && msm_pm_sleep_modes[idx].suspend_supported; } EXPORT_SYMBOL(msm_pm_sleep_mode_allow);