You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
795 lines
18 KiB
795 lines
18 KiB
/* Copyright (c) 2014-2020, 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.
|
|
*
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, KBUILD_MODNAME
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/of.h>
|
|
#include <linux/err.h>
|
|
#include <linux/sysfs.h>
|
|
#include <linux/device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/moduleparam.h>
|
|
#include "lpm-levels.h"
|
|
|
|
#if defined(CONFIG_SEC_A90Q_PROJECT)
|
|
extern bool pd_charging;
|
|
#endif
|
|
|
|
enum lpm_type {
|
|
IDLE = 0,
|
|
SUSPEND,
|
|
LATENCY,
|
|
LPM_TYPE_NR
|
|
};
|
|
|
|
struct lpm_type_str {
|
|
enum lpm_type type;
|
|
char *str;
|
|
};
|
|
|
|
static const struct lpm_type_str lpm_types[] = {
|
|
{IDLE, "idle_enabled"},
|
|
{SUSPEND, "suspend_enabled"},
|
|
{LATENCY, "exit_latency_us"},
|
|
};
|
|
|
|
static struct lpm_level_avail *cpu_level_available[NR_CPUS];
|
|
static struct platform_device *lpm_pdev;
|
|
|
|
static void *get_enabled_ptr(struct kobj_attribute *attr,
|
|
struct lpm_level_avail *avail)
|
|
{
|
|
void *arg = NULL;
|
|
|
|
if (!strcmp(attr->attr.name, lpm_types[IDLE].str))
|
|
arg = (void *) &avail->idle_enabled;
|
|
else if (!strcmp(attr->attr.name, lpm_types[SUSPEND].str))
|
|
arg = (void *) &avail->suspend_enabled;
|
|
|
|
return arg;
|
|
}
|
|
|
|
static struct lpm_level_avail *get_avail_ptr(struct kobject *kobj,
|
|
struct kobj_attribute *attr)
|
|
{
|
|
struct lpm_level_avail *avail = NULL;
|
|
|
|
if (!strcmp(attr->attr.name, lpm_types[IDLE].str))
|
|
avail = container_of(attr, struct lpm_level_avail,
|
|
idle_enabled_attr);
|
|
else if (!strcmp(attr->attr.name, lpm_types[SUSPEND].str))
|
|
avail = container_of(attr, struct lpm_level_avail,
|
|
suspend_enabled_attr);
|
|
else if (!strcmp(attr->attr.name, lpm_types[LATENCY].str))
|
|
avail = container_of(attr, struct lpm_level_avail,
|
|
latency_attr);
|
|
|
|
return avail;
|
|
}
|
|
|
|
static ssize_t lpm_latency_show(struct kobject *kobj,
|
|
struct kobj_attribute *attr, char *buf)
|
|
{
|
|
int ret = 0;
|
|
struct kernel_param kp;
|
|
struct lpm_level_avail *avail = get_avail_ptr(kobj, attr);
|
|
|
|
if (WARN_ON(!avail))
|
|
return -EINVAL;
|
|
|
|
kp.arg = &avail->exit_latency;
|
|
|
|
ret = param_get_uint(buf, &kp);
|
|
if (ret > 0) {
|
|
strlcat(buf, "\n", PAGE_SIZE);
|
|
ret++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
ssize_t lpm_enable_show(struct kobject *kobj, struct kobj_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int ret = 0;
|
|
struct kernel_param kp;
|
|
struct lpm_level_avail *avail = get_avail_ptr(kobj, attr);
|
|
|
|
if (WARN_ON(!avail))
|
|
return -EINVAL;
|
|
|
|
kp.arg = get_enabled_ptr(attr, avail);
|
|
if (WARN_ON(!kp.arg))
|
|
return -EINVAL;
|
|
|
|
ret = param_get_bool(buf, &kp);
|
|
if (ret > 0) {
|
|
strlcat(buf, "\n", PAGE_SIZE);
|
|
ret++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
ssize_t lpm_enable_store(struct kobject *kobj, struct kobj_attribute *attr,
|
|
const char *buf, size_t len)
|
|
{
|
|
int ret = 0;
|
|
struct kernel_param kp;
|
|
struct lpm_level_avail *avail;
|
|
|
|
avail = get_avail_ptr(kobj, attr);
|
|
if (WARN_ON(!avail))
|
|
return -EINVAL;
|
|
|
|
kp.arg = get_enabled_ptr(attr, avail);
|
|
ret = param_set_bool(buf, &kp);
|
|
|
|
return ret ? ret : len;
|
|
}
|
|
|
|
static int create_lvl_avail_nodes(const char *name,
|
|
struct kobject *parent, struct lpm_level_avail *avail,
|
|
void *data, int index, bool cpu_node)
|
|
{
|
|
struct attribute_group *attr_group = NULL;
|
|
struct attribute **attr = NULL;
|
|
struct kobject *kobj = NULL;
|
|
int ret = 0;
|
|
|
|
kobj = kobject_create_and_add(name, parent);
|
|
if (!kobj)
|
|
return -ENOMEM;
|
|
|
|
attr_group = devm_kzalloc(&lpm_pdev->dev, sizeof(*attr_group),
|
|
GFP_KERNEL);
|
|
if (!attr_group) {
|
|
ret = -ENOMEM;
|
|
goto failed;
|
|
}
|
|
|
|
attr = devm_kzalloc(&lpm_pdev->dev,
|
|
sizeof(*attr) * (LPM_TYPE_NR + 1), GFP_KERNEL);
|
|
if (!attr) {
|
|
ret = -ENOMEM;
|
|
goto failed;
|
|
}
|
|
|
|
sysfs_attr_init(&avail->idle_enabled_attr.attr);
|
|
avail->idle_enabled_attr.attr.name = lpm_types[IDLE].str;
|
|
avail->idle_enabled_attr.attr.mode = 0644;
|
|
avail->idle_enabled_attr.show = lpm_enable_show;
|
|
avail->idle_enabled_attr.store = lpm_enable_store;
|
|
|
|
sysfs_attr_init(&avail->suspend_enabled_attr.attr);
|
|
avail->suspend_enabled_attr.attr.name = lpm_types[SUSPEND].str;
|
|
avail->suspend_enabled_attr.attr.mode = 0644;
|
|
avail->suspend_enabled_attr.show = lpm_enable_show;
|
|
avail->suspend_enabled_attr.store = lpm_enable_store;
|
|
|
|
sysfs_attr_init(&avail->latency_attr.attr);
|
|
avail->latency_attr.attr.name = lpm_types[LATENCY].str;
|
|
avail->latency_attr.attr.mode = 0444;
|
|
avail->latency_attr.show = lpm_latency_show;
|
|
avail->latency_attr.store = NULL;
|
|
|
|
attr[0] = &avail->idle_enabled_attr.attr;
|
|
attr[1] = &avail->suspend_enabled_attr.attr;
|
|
attr[2] = &avail->latency_attr.attr;
|
|
attr[3] = NULL;
|
|
attr_group->attrs = attr;
|
|
|
|
ret = sysfs_create_group(kobj, attr_group);
|
|
if (ret) {
|
|
ret = -ENOMEM;
|
|
goto failed;
|
|
}
|
|
|
|
avail->idle_enabled = true;
|
|
avail->suspend_enabled = true;
|
|
avail->kobj = kobj;
|
|
avail->data = data;
|
|
avail->idx = index;
|
|
avail->cpu_node = cpu_node;
|
|
|
|
return ret;
|
|
|
|
failed:
|
|
kobject_put(kobj);
|
|
return ret;
|
|
}
|
|
|
|
static int create_cpu_lvl_nodes(struct lpm_cluster *p, struct kobject *parent)
|
|
{
|
|
int cpu;
|
|
int i, cpu_idx;
|
|
struct kobject **cpu_kobj = NULL;
|
|
struct lpm_level_avail *level_list = NULL;
|
|
char cpu_name[20] = {0};
|
|
int ret = 0;
|
|
struct list_head *pos;
|
|
|
|
cpu_kobj = devm_kzalloc(&lpm_pdev->dev, sizeof(*cpu_kobj) *
|
|
cpumask_weight(&p->child_cpus), GFP_KERNEL);
|
|
if (!cpu_kobj)
|
|
return -ENOMEM;
|
|
|
|
cpu_idx = 0;
|
|
list_for_each(pos, &p->cpu) {
|
|
struct lpm_cpu *lpm_cpu = list_entry(pos, struct lpm_cpu, list);
|
|
|
|
for_each_cpu(cpu, &lpm_cpu->related_cpus) {
|
|
snprintf(cpu_name, sizeof(cpu_name), "cpu%d", cpu);
|
|
cpu_kobj[cpu_idx] = kobject_create_and_add(cpu_name,
|
|
parent);
|
|
if (!cpu_kobj[cpu_idx]) {
|
|
ret = -ENOMEM;
|
|
goto release_kobj;
|
|
}
|
|
|
|
level_list = devm_kzalloc(&lpm_pdev->dev,
|
|
lpm_cpu->nlevels * sizeof(*level_list),
|
|
GFP_KERNEL);
|
|
if (!level_list) {
|
|
ret = -ENOMEM;
|
|
goto release_kobj;
|
|
}
|
|
|
|
/*
|
|
* Skip enable/disable for WFI. cpuidle expects WFI to
|
|
* be available at all times.
|
|
*/
|
|
for (i = 1; i < lpm_cpu->nlevels; i++) {
|
|
level_list[i].exit_latency =
|
|
p->levels[i].pwr.exit_latency;
|
|
ret = create_lvl_avail_nodes(
|
|
lpm_cpu->levels[i].name,
|
|
cpu_kobj[cpu_idx],
|
|
&level_list[i],
|
|
(void *)lpm_cpu, cpu, true);
|
|
if (ret)
|
|
goto release_kobj;
|
|
}
|
|
|
|
cpu_level_available[cpu] = level_list;
|
|
cpu_idx++;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
|
|
release_kobj:
|
|
for (i = 0; i < cpumask_weight(&p->child_cpus); i++)
|
|
kobject_put(cpu_kobj[i]);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int create_cluster_lvl_nodes(struct lpm_cluster *p, struct kobject *kobj)
|
|
{
|
|
int ret = 0;
|
|
struct lpm_cluster *child = NULL;
|
|
int i;
|
|
struct kobject *cluster_kobj = NULL;
|
|
|
|
if (!p)
|
|
return -ENODEV;
|
|
|
|
cluster_kobj = kobject_create_and_add(p->cluster_name, kobj);
|
|
if (!cluster_kobj)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < p->nlevels; i++) {
|
|
p->levels[i].available.exit_latency =
|
|
p->levels[i].pwr.exit_latency;
|
|
ret = create_lvl_avail_nodes(p->levels[i].level_name,
|
|
cluster_kobj, &p->levels[i].available,
|
|
(void *)p, 0, false);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
list_for_each_entry(child, &p->child, list) {
|
|
ret = create_cluster_lvl_nodes(child, cluster_kobj);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (!list_empty(&p->cpu)) {
|
|
ret = create_cpu_lvl_nodes(p, cluster_kobj);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool lpm_cpu_mode_allow(unsigned int cpu,
|
|
unsigned int index, bool from_idle)
|
|
{
|
|
struct lpm_level_avail *avail = cpu_level_available[cpu];
|
|
|
|
if (lpm_pdev && !index)
|
|
return 1;
|
|
|
|
if (!lpm_pdev || !avail)
|
|
return !from_idle;
|
|
|
|
return !!(from_idle ? avail[index].idle_enabled :
|
|
avail[index].suspend_enabled);
|
|
}
|
|
|
|
bool lpm_cluster_mode_allow(struct lpm_cluster *cluster,
|
|
unsigned int mode, bool from_idle)
|
|
{
|
|
struct lpm_level_avail *avail = &cluster->levels[mode].available;
|
|
|
|
if (!lpm_pdev || !avail)
|
|
return false;
|
|
|
|
#if defined(CONFIG_SEC_A90Q_PROJECT)
|
|
if (pd_charging && ((mode == 2) || (mode == 3)))
|
|
return false;
|
|
#endif
|
|
|
|
return !!(from_idle ? avail->idle_enabled :
|
|
avail->suspend_enabled);
|
|
}
|
|
|
|
static int parse_cluster_params(struct device_node *node,
|
|
struct lpm_cluster *c)
|
|
{
|
|
char *key;
|
|
int ret;
|
|
|
|
key = "label";
|
|
ret = of_property_read_string(node, key, &c->cluster_name);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
key = "qcom,psci-mode-shift";
|
|
ret = of_property_read_u32(node, key, &c->psci_mode_shift);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
key = "qcom,psci-mode-mask";
|
|
ret = of_property_read_u32(node, key, &c->psci_mode_mask);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
key = "qcom,disable-prediction";
|
|
c->lpm_prediction = !(of_property_read_bool(node, key));
|
|
|
|
if (c->lpm_prediction) {
|
|
key = "qcom,clstr-tmr-add";
|
|
ret = of_property_read_u32(node, key, &c->tmr_add);
|
|
if (ret || c->tmr_add < TIMER_ADD_LOW ||
|
|
c->tmr_add > TIMER_ADD_HIGH)
|
|
c->tmr_add = DEFAULT_TIMER_ADD;
|
|
}
|
|
|
|
/* Set default_level to 0 as default */
|
|
c->default_level = 0;
|
|
|
|
return 0;
|
|
fail:
|
|
pr_err("Failed to read key: %s ret: %d\n", key, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int parse_power_params(struct device_node *node,
|
|
struct power_params *pwr)
|
|
{
|
|
char *key;
|
|
int ret;
|
|
|
|
key = "qcom,entry-latency-us";
|
|
ret = of_property_read_u32(node, key, &pwr->entry_latency);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
key = "qcom,exit-latency-us";
|
|
ret = of_property_read_u32(node, key, &pwr->exit_latency);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
key = "qcom,min-residency-us";
|
|
ret = of_property_read_u32(node, key, &pwr->min_residency);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
return ret;
|
|
fail:
|
|
pr_err("Failed to read key: %s node: %s\n", key, node->name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int parse_cluster_level(struct device_node *node,
|
|
struct lpm_cluster *cluster)
|
|
{
|
|
struct lpm_cluster_level *level = &cluster->levels[cluster->nlevels];
|
|
int ret = -ENOMEM;
|
|
char *key;
|
|
|
|
key = "label";
|
|
ret = of_property_read_string(node, key, &level->level_name);
|
|
if (ret)
|
|
goto failed;
|
|
|
|
key = "qcom,psci-mode";
|
|
ret = of_property_read_u32(node, key, &level->psci_id);
|
|
if (ret)
|
|
goto failed;
|
|
|
|
level->is_reset = of_property_read_bool(node, "qcom,is-reset");
|
|
|
|
if (cluster->nlevels != cluster->default_level) {
|
|
key = "qcom,min-child-idx";
|
|
ret = of_property_read_u32(node, key, &level->min_child_level);
|
|
if (ret)
|
|
goto failed;
|
|
|
|
if (cluster->min_child_level > level->min_child_level)
|
|
cluster->min_child_level = level->min_child_level;
|
|
}
|
|
|
|
level->notify_rpm = of_property_read_bool(node, "qcom,notify-rpm");
|
|
|
|
key = "parse_power_params";
|
|
ret = parse_power_params(node, &level->pwr);
|
|
if (ret)
|
|
goto failed;
|
|
|
|
key = "qcom,reset-level";
|
|
ret = of_property_read_u32(node, key, &level->reset_level);
|
|
if (ret == -EINVAL)
|
|
level->reset_level = LPM_RESET_LVL_NONE;
|
|
else if (ret)
|
|
goto failed;
|
|
|
|
cluster->nlevels++;
|
|
|
|
return 0;
|
|
failed:
|
|
pr_err("Failed to read key: %s ret: %d\n", key, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int parse_cpu_mode(struct device_node *n, struct lpm_cpu_level *l)
|
|
{
|
|
char *key;
|
|
int ret;
|
|
|
|
key = "label";
|
|
ret = of_property_read_string(n, key, &l->name);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
key = "qcom,psci-cpu-mode";
|
|
ret = of_property_read_u32(n, key, &l->psci_id);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
return ret;
|
|
fail:
|
|
pr_err("Failed to read key: %s level: %s\n", key, l->name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int get_cpumask_for_node(struct device_node *node, struct cpumask *mask)
|
|
{
|
|
struct device_node *cpu_node;
|
|
int cpu;
|
|
int idx = 0;
|
|
|
|
cpu_node = of_parse_phandle(node, "qcom,cpu", idx++);
|
|
if (!cpu_node) {
|
|
pr_info("%s: No CPU phandle, assuming single cluster\n",
|
|
node->full_name);
|
|
/*
|
|
* Not all targets have the cpu node populated in the device
|
|
* tree. If cpu node is not populated assume all possible
|
|
* nodes belong to this cluster
|
|
*/
|
|
cpumask_copy(mask, cpu_possible_mask);
|
|
return 0;
|
|
}
|
|
|
|
while (cpu_node) {
|
|
for_each_possible_cpu(cpu) {
|
|
if (of_get_cpu_node(cpu, NULL) == cpu_node) {
|
|
cpumask_set_cpu(cpu, mask);
|
|
break;
|
|
}
|
|
}
|
|
of_node_put(cpu_node);
|
|
cpu_node = of_parse_phandle(node, "qcom,cpu", idx++);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_cpu(struct device_node *node, struct lpm_cpu *cpu)
|
|
{
|
|
|
|
struct device_node *n;
|
|
int ret, i;
|
|
const char *key;
|
|
|
|
for_each_child_of_node(node, n) {
|
|
struct lpm_cpu_level *l = &cpu->levels[cpu->nlevels];
|
|
|
|
cpu->nlevels++;
|
|
|
|
ret = parse_cpu_mode(n, l);
|
|
if (ret) {
|
|
of_node_put(n);
|
|
return ret;
|
|
}
|
|
|
|
ret = parse_power_params(n, &l->pwr);
|
|
if (ret) {
|
|
of_node_put(n);
|
|
return ret;
|
|
}
|
|
key = "qcom,use-broadcast-timer";
|
|
l->use_bc_timer = of_property_read_bool(n, key);
|
|
|
|
key = "qcom,is-reset";
|
|
l->is_reset = of_property_read_bool(n, key);
|
|
|
|
key = "qcom,reset-level";
|
|
ret = of_property_read_u32(n, key, &l->reset_level);
|
|
of_node_put(n);
|
|
if (ret == -EINVAL)
|
|
l->reset_level = LPM_RESET_LVL_NONE;
|
|
else if (ret)
|
|
return ret;
|
|
}
|
|
|
|
for (i = 1; i < cpu->nlevels; i++)
|
|
cpu->levels[i-1].pwr.max_residency =
|
|
cpu->levels[i].pwr.min_residency - 1;
|
|
|
|
cpu->levels[i-1].pwr.max_residency = UINT_MAX;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_cpu_levels(struct device_node *node, struct lpm_cluster *c)
|
|
{
|
|
int ret;
|
|
char *key;
|
|
struct lpm_cpu *cpu;
|
|
|
|
cpu = devm_kzalloc(&lpm_pdev->dev, sizeof(*cpu), GFP_KERNEL);
|
|
if (!cpu)
|
|
return -ENOMEM;
|
|
|
|
if (get_cpumask_for_node(node, &cpu->related_cpus))
|
|
return -EINVAL;
|
|
|
|
cpu->parent = c;
|
|
|
|
key = "qcom,psci-mode-shift";
|
|
ret = of_property_read_u32(node, key, &cpu->psci_mode_shift);
|
|
if (ret)
|
|
goto failed;
|
|
|
|
key = "qcom,psci-mode-mask";
|
|
ret = of_property_read_u32(node, key, &cpu->psci_mode_mask);
|
|
if (ret)
|
|
goto failed;
|
|
|
|
cpu->ipi_prediction = !(of_property_read_bool(node,
|
|
"qcom,disable-ipi-prediction"));
|
|
|
|
cpu->lpm_prediction = !(of_property_read_bool(node,
|
|
"qcom,disable-prediction"));
|
|
|
|
if (cpu->lpm_prediction) {
|
|
key = "qcom,ref-stddev";
|
|
ret = of_property_read_u32(node, key, &cpu->ref_stddev);
|
|
if (ret || cpu->ref_stddev < STDDEV_LOW ||
|
|
cpu->ref_stddev > STDDEV_HIGH)
|
|
cpu->ref_stddev = DEFAULT_STDDEV;
|
|
|
|
key = "qcom,tmr-add";
|
|
ret = of_property_read_u32(node, key, &cpu->tmr_add);
|
|
if (ret || cpu->tmr_add < TIMER_ADD_LOW ||
|
|
cpu->tmr_add > TIMER_ADD_HIGH)
|
|
cpu->tmr_add = DEFAULT_TIMER_ADD;
|
|
|
|
key = "qcom,ref-premature-cnt";
|
|
ret = of_property_read_u32(node, key, &cpu->ref_premature_cnt);
|
|
if (ret || cpu->ref_premature_cnt < PREMATURE_CNT_LOW ||
|
|
cpu->ref_premature_cnt > PREMATURE_CNT_HIGH)
|
|
cpu->ref_premature_cnt = DEFAULT_PREMATURE_CNT;
|
|
}
|
|
|
|
key = "parse_cpu";
|
|
ret = parse_cpu(node, cpu);
|
|
if (ret)
|
|
goto failed;
|
|
|
|
cpumask_or(&c->child_cpus, &c->child_cpus, &cpu->related_cpus);
|
|
list_add(&cpu->list, &c->cpu);
|
|
|
|
return ret;
|
|
|
|
failed:
|
|
pr_err("Failed to read key: %s node: %s\n", key, node->name);
|
|
return ret;
|
|
}
|
|
|
|
void free_cluster_node(struct lpm_cluster *cluster)
|
|
{
|
|
struct lpm_cpu *cpu, *n;
|
|
struct lpm_cluster *cl, *m;
|
|
|
|
list_for_each_entry_safe(cl, m, &cluster->child, list) {
|
|
list_del(&cl->list);
|
|
free_cluster_node(cl);
|
|
};
|
|
|
|
list_for_each_entry_safe(cpu, n, &cluster->cpu, list)
|
|
list_del(&cpu->list);
|
|
}
|
|
|
|
/*
|
|
* TODO:
|
|
* Expects a CPU or a cluster only. This ensures that affinity
|
|
* level of a cluster is consistent with reference to its
|
|
* child nodes.
|
|
*/
|
|
struct lpm_cluster *parse_cluster(struct device_node *node,
|
|
struct lpm_cluster *parent)
|
|
{
|
|
struct lpm_cluster *c;
|
|
struct device_node *n;
|
|
char *key;
|
|
int ret = 0, i;
|
|
|
|
c = devm_kzalloc(&lpm_pdev->dev, sizeof(*c), GFP_KERNEL);
|
|
if (!c)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
ret = parse_cluster_params(node, c);
|
|
if (ret)
|
|
return NULL;
|
|
|
|
INIT_LIST_HEAD(&c->child);
|
|
INIT_LIST_HEAD(&c->cpu);
|
|
c->parent = parent;
|
|
spin_lock_init(&c->sync_lock);
|
|
c->min_child_level = NR_LPM_LEVELS;
|
|
|
|
for_each_child_of_node(node, n) {
|
|
|
|
if (!n->name) {
|
|
of_node_put(n);
|
|
continue;
|
|
}
|
|
|
|
if (!of_node_cmp(n->name, "qcom,pm-cluster-level")) {
|
|
key = "qcom,pm-cluster-level";
|
|
if (parse_cluster_level(n, c))
|
|
goto failed_parse_cluster;
|
|
} else if (!of_node_cmp(n->name, "qcom,pm-cluster")) {
|
|
struct lpm_cluster *child;
|
|
|
|
key = "qcom,pm-cluster";
|
|
child = parse_cluster(n, c);
|
|
if (!child)
|
|
goto failed_parse_cluster;
|
|
|
|
list_add(&child->list, &c->child);
|
|
cpumask_or(&c->child_cpus, &c->child_cpus,
|
|
&child->child_cpus);
|
|
c->aff_level = child->aff_level + 1;
|
|
} else if (!of_node_cmp(n->name, "qcom,pm-cpu")) {
|
|
key = "qcom,pm-cpu";
|
|
if (parse_cpu_levels(n, c))
|
|
goto failed_parse_cluster;
|
|
|
|
c->aff_level = 1;
|
|
}
|
|
|
|
of_node_put(n);
|
|
}
|
|
|
|
if (cpumask_intersects(&c->child_cpus, cpu_online_mask))
|
|
c->last_level = c->default_level;
|
|
else
|
|
c->last_level = c->nlevels-1;
|
|
|
|
for (i = 1; i < c->nlevels; i++)
|
|
c->levels[i-1].pwr.max_residency =
|
|
c->levels[i].pwr.min_residency - 1;
|
|
|
|
c->levels[i-1].pwr.max_residency = UINT_MAX;
|
|
|
|
return c;
|
|
|
|
failed_parse_cluster:
|
|
pr_err("Failed parse cluster:%s\n", key);
|
|
of_node_put(n);
|
|
if (parent)
|
|
list_del(&c->list);
|
|
free_cluster_node(c);
|
|
return NULL;
|
|
}
|
|
|
|
struct lpm_cluster *lpm_of_parse_cluster(struct platform_device *pdev)
|
|
{
|
|
struct device_node *top = NULL;
|
|
struct lpm_cluster *c;
|
|
|
|
top = of_find_node_by_name(pdev->dev.of_node, "qcom,pm-cluster");
|
|
if (!top) {
|
|
pr_err("Failed to find root node\n");
|
|
return ERR_PTR(-ENODEV);
|
|
}
|
|
|
|
lpm_pdev = pdev;
|
|
c = parse_cluster(top, NULL);
|
|
of_node_put(top);
|
|
return c;
|
|
}
|
|
|
|
void cluster_dt_walkthrough(struct lpm_cluster *cluster)
|
|
{
|
|
struct list_head *list;
|
|
struct lpm_cpu *cpu;
|
|
int i, j;
|
|
static int id;
|
|
char str[10] = {0};
|
|
|
|
if (!cluster)
|
|
return;
|
|
|
|
for (i = 0; i < id; i++)
|
|
snprintf(str+i, 10 - i, "\t");
|
|
pr_info("%d\n", __LINE__);
|
|
|
|
for (i = 0; i < cluster->nlevels; i++) {
|
|
struct lpm_cluster_level *l = &cluster->levels[i];
|
|
pr_info("cluster: %s \t level: %s\n", cluster->cluster_name,
|
|
l->level_name);
|
|
}
|
|
|
|
list_for_each_entry(cpu, &cluster->cpu, list) {
|
|
pr_info("%d\n", __LINE__);
|
|
for (j = 0; j < cpu->nlevels; j++)
|
|
pr_info("%s\tCPU level name: %s\n", str,
|
|
cpu->levels[j].name);
|
|
}
|
|
|
|
id++;
|
|
|
|
list_for_each(list, &cluster->child) {
|
|
struct lpm_cluster *n;
|
|
|
|
pr_info("%d\n", __LINE__);
|
|
n = list_entry(list, typeof(*n), list);
|
|
cluster_dt_walkthrough(n);
|
|
}
|
|
id--;
|
|
}
|
|
|