439 lines
11 KiB
C
439 lines
11 KiB
C
/* 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 <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <soc/qcom/pm.h>
|
|
|
|
#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);
|