drivers: cpuidle: lpm-levels: Support for different CPUs in a cluster

The newer FCM microarchitecture allows for CPUs with different
performance and power characteristics to be part of the same cluster.
The CPUs have different power performances, like latency and residency
for different low power mode.

Adding support to differentiate between CPUs within a single cluster.

Change-Id: I61fbe6d1b9e6963b5db269a079254be82a7b9d3c
Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
diff --git a/drivers/cpuidle/lpm-levels-of.c b/drivers/cpuidle/lpm-levels-of.c
index 2201196c..39e0484 100644
--- a/drivers/cpuidle/lpm-levels-of.c
+++ b/drivers/cpuidle/lpm-levels-of.c
@@ -305,6 +305,7 @@
 	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);
@@ -312,38 +313,45 @@
 		return -ENOMEM;
 
 	cpu_idx = 0;
-	for_each_cpu(cpu, &p->child_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;
-		}
+	list_for_each(pos, &p->cpu) {
+		struct lpm_cpu *lpm_cpu = list_entry(pos, struct lpm_cpu, list);
 
-		level_list = devm_kzalloc(&lpm_pdev->dev,
-				p->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 < p->cpu->nlevels; i++) {
-
-			level_list[i].latency_us = p->levels[i].pwr.latency_us;
-			ret = create_lvl_avail_nodes(p->cpu->levels[i].name,
-					cpu_kobj[cpu_idx], &level_list[i],
-					(void *)p->cpu, cpu, true);
-			if (ret)
+		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;
-		}
+			}
 
-		cpu_level_available[cpu] = level_list;
-		cpu_idx++;
+			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].latency_us =
+					p->levels[i].pwr.latency_us;
+				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;
@@ -384,7 +392,7 @@
 			return ret;
 	}
 
-	if (p->cpu) {
+	if (!list_empty(&p->cpu)) {
 		ret = create_cpu_lvl_nodes(p, cluster_kobj);
 		if (ret)
 			return ret;
@@ -619,49 +627,26 @@
 				next_pwr->time_overhead_us : residency;
 }
 
-static int parse_cpu_levels(struct device_node *node, struct lpm_cluster *c)
+static int parse_cpu(struct device_node *node, struct lpm_cpu *cpu)
 {
+
 	struct device_node *n;
-	int ret = -ENOMEM;
-	int i, j;
-	char *key;
-
-	c->cpu = devm_kzalloc(&lpm_pdev->dev, sizeof(*c->cpu), GFP_KERNEL);
-	if (!c->cpu)
-		return ret;
-
-	c->cpu->parent = c;
-
-	key = "qcom,psci-mode-shift";
-
-	ret = of_property_read_u32(node, key, &c->cpu->psci_mode_shift);
-	if (ret) {
-		pr_err("Failed reading %s on device %s\n", key,
-				node->name);
-		return ret;
-	}
-	key = "qcom,psci-mode-mask";
-
-	ret = of_property_read_u32(node, key, &c->cpu->psci_mode_mask);
-	if (ret) {
-		pr_err("Failed reading %s on device %s\n", key,
-				node->name);
-		return ret;
-	}
+	int ret, i, j;
+	const char *key;
 	for_each_child_of_node(node, n) {
-		struct lpm_cpu_level *l = &c->cpu->levels[c->cpu->nlevels];
+		struct lpm_cpu_level *l = &cpu->levels[cpu->nlevels];
 
-		c->cpu->nlevels++;
+		cpu->nlevels++;
 
 		ret = parse_cpu_mode(n, l);
 		if (ret < 0) {
 			pr_info("Failed %s\n", l->name);
-			goto failed;
+			return ret;
 		}
 
 		ret = parse_power_params(n, &l->pwr);
 		if (ret)
-			goto failed;
+			return ret;
 
 		key = "qcom,use-broadcast-timer";
 		l->use_bc_timer = of_property_read_bool(n, key);
@@ -676,32 +661,83 @@
 		if (ret == -EINVAL)
 			l->reset_level = LPM_RESET_LVL_NONE;
 		else if (ret)
-			goto failed;
+			return ret;
 	}
-	for (i = 0; i < c->cpu->nlevels; i++) {
-		for (j = 0; j < c->cpu->nlevels; j++) {
+	for (i = 0; i < cpu->nlevels; i++) {
+		for (j = 0; j < cpu->nlevels; j++) {
 			if (i >= j) {
-				c->cpu->levels[i].pwr.residencies[j] = 0;
+				cpu->levels[i].pwr.residencies[j] = 0;
 				continue;
 			}
 
-			c->cpu->levels[i].pwr.residencies[j] =
-				calculate_residency(&c->cpu->levels[i].pwr,
-						&c->cpu->levels[j].pwr);
+			cpu->levels[i].pwr.residencies[j] =
+				calculate_residency(&cpu->levels[i].pwr,
+						&cpu->levels[j].pwr);
 
 			pr_err("%s: idx %d %u\n", __func__, j,
-					c->cpu->levels[i].pwr.residencies[j]);
+					cpu->levels[i].pwr.residencies[j]);
 		}
 	}
+	for_each_cpu(i, &cpu->related_cpus) {
+		per_cpu(max_residency, i) = devm_kzalloc(&lpm_pdev->dev,
+				sizeof(uint32_t) * cpu->nlevels,
+				GFP_KERNEL);
+		if (!per_cpu(max_residency, i))
+			return -ENOMEM;
+		per_cpu(min_residency, i) = devm_kzalloc(
+				&lpm_pdev->dev,
+				sizeof(uint32_t) * cpu->nlevels,
+				GFP_KERNEL);
+		if (!per_cpu(min_residency, i))
+			return -ENOMEM;
+		set_optimum_cpu_residency(cpu, i, true);
+	}
 
 	return 0;
-failed:
-	for (i = 0; i < c->cpu->nlevels; i++) {
-		kfree(c->cpu->levels[i].name);
-		c->cpu->levels[i].name = NULL;
+}
+
+static int parse_cpu_levels(struct device_node *node, struct lpm_cluster *c)
+{
+	int ret = -ENOMEM, i;
+	char *key;
+	struct lpm_cpu *cpu;
+
+	cpu = devm_kzalloc(&lpm_pdev->dev, sizeof(*cpu), GFP_KERNEL);
+	if (!cpu)
+		return ret;
+
+	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) {
+		pr_err("Failed reading %s on device %s\n", key,
+				node->name);
+		return ret;
 	}
-	kfree(c->cpu);
-	c->cpu = NULL;
+	key = "qcom,psci-mode-mask";
+
+	ret = of_property_read_u32(node, key, &cpu->psci_mode_mask);
+	if (ret) {
+		pr_err("Failed reading %s on device %s\n", key,
+				node->name);
+		return ret;
+	}
+
+	if (parse_cpu(node, cpu))
+		goto failed;
+	cpumask_or(&c->child_cpus, &c->child_cpus, &cpu->related_cpus);
+	list_add(&cpu->list, &c->cpu);
+	return 0;
+failed:
+	for (i = 0; i < cpu->nlevels; i++) {
+		kfree(cpu->levels[i].name);
+		cpu->levels[i].name = NULL;
+	}
+	kfree(cpu);
 	pr_err("%s(): Failed with error code:%d\n", __func__, ret);
 	return ret;
 }
@@ -709,6 +745,7 @@
 void free_cluster_node(struct lpm_cluster *cluster)
 {
 	struct list_head *list;
+	struct lpm_cpu *cpu, *n;
 	int i;
 
 	list_for_each(list, &cluster->child) {
@@ -719,19 +756,20 @@
 		free_cluster_node(n);
 	};
 
-	if (cluster->cpu) {
-		for (i = 0; i < cluster->cpu->nlevels; i++) {
-			kfree(cluster->cpu->levels[i].name);
-			cluster->cpu->levels[i].name = NULL;
+	list_for_each_entry_safe(cpu, n, &cluster->cpu, list) {
+		struct lpm_cpu *cpu = list_entry(list, typeof(*cpu), list);
+
+		for (i = 0; i < cpu->nlevels; i++) {
+			kfree(cpu->levels[i].name);
+			cpu->levels[i].name = NULL;
 		}
+		list_del(list);
 	}
 	for (i = 0; i < cluster->nlevels; i++) {
 		kfree(cluster->levels[i].mode);
 		cluster->levels[i].mode = NULL;
 	}
-	kfree(cluster->cpu);
 	kfree(cluster->name);
-	cluster->cpu = NULL;
 	cluster->name = NULL;
 	cluster->ndevices = 0;
 }
@@ -761,6 +799,7 @@
 		goto failed_parse_params;
 
 	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;
@@ -793,34 +832,11 @@
 
 		key = "qcom,pm-cpu";
 		if (!of_node_cmp(n->name, key)) {
-			/*
-			 * Parse the the cpu node only if a pm-cpu node
-			 * is available, though the mask is defined @ the
-			 * cluster level
-			 */
-			if (get_cpumask_for_node(node, &c->child_cpus))
-				goto failed_parse_cluster;
-
 			if (parse_cpu_levels(n, c))
 				goto failed_parse_cluster;
 
 			c->aff_level = 1;
 
-			for_each_cpu(i, &c->child_cpus) {
-				per_cpu(max_residency, i) = devm_kzalloc(
-					&lpm_pdev->dev,
-					sizeof(uint32_t) * c->cpu->nlevels,
-					GFP_KERNEL);
-				if (!per_cpu(max_residency, i))
-					return ERR_PTR(-ENOMEM);
-				per_cpu(min_residency, i) = devm_kzalloc(
-					&lpm_pdev->dev,
-					sizeof(uint32_t) * c->cpu->nlevels,
-					GFP_KERNEL);
-				if (!per_cpu(min_residency, i))
-					return ERR_PTR(-ENOMEM);
-				set_optimum_cpu_residency(c->cpu, i, true);
-			}
 		}
 	}
 
@@ -870,6 +886,7 @@
 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};
@@ -890,12 +907,12 @@
 					&cluster->name[j], &l->mode[i]);
 	}
 
-	if (cluster->cpu) {
+	list_for_each_entry(cpu, &cluster->cpu, list) {
 		pr_info("%d\n", __LINE__);
-		for (j = 0; j < cluster->cpu->nlevels; j++)
+		for (j = 0; j < cpu->nlevels; j++)
 			pr_info("%s\tCPU mode: %s id:%d\n", str,
-					cluster->cpu->levels[j].name,
-					cluster->cpu->levels[j].mode);
+					cpu->levels[j].name,
+					cpu->levels[j].mode);
 	}
 
 	id++;
diff --git a/drivers/cpuidle/lpm-levels.c b/drivers/cpuidle/lpm-levels.c
index 1a01e3f..79f6c52 100644
--- a/drivers/cpuidle/lpm-levels.c
+++ b/drivers/cpuidle/lpm-levels.c
@@ -104,7 +104,7 @@
 
 static DEFINE_PER_CPU(struct lpm_history, hist);
 
-static DEFINE_PER_CPU(struct lpm_cluster*, cpu_cluster);
+static DEFINE_PER_CPU(struct lpm_cpu*, cpu_lpm);
 static bool suspend_in_progress;
 static struct hrtimer lpm_hrtimer;
 static struct hrtimer histtimer;
@@ -207,7 +207,7 @@
 	struct power_params *pwr_params;
 	struct lpm_cpu *cpu;
 	struct lpm_cluster *n;
-	uint32_t latency = 0;
+	uint32_t lat = 0;
 	int i;
 
 	list_for_each(list, child) {
@@ -216,19 +216,21 @@
 			if (strcmp(lat_level->level_name, n->cluster_name))
 				continue;
 		}
-		cpu = n->cpu;
-		for (i = 0; i < cpu->nlevels; i++) {
-			level = &cpu->levels[i];
-			pwr_params = &level->pwr;
-			if (lat_level->reset_level == level->reset_level) {
-				if ((latency > pwr_params->latency_us)
-							|| (!latency))
-					latency = pwr_params->latency_us;
-				break;
+		list_for_each_entry(cpu, &n->cpu, list) {
+			for (i = 0; i < cpu->nlevels; i++) {
+				level = &cpu->levels[i];
+				pwr_params = &level->pwr;
+				if (lat_level->reset_level
+						== level->reset_level) {
+					if ((lat > pwr_params->latency_us)
+							|| (!lat))
+						lat = pwr_params->latency_us;
+					break;
+				}
 			}
 		}
 	}
-	return latency;
+	return lat;
 }
 
 static struct lpm_cluster *cluster_aff_match(struct lpm_cluster *cluster,
@@ -237,9 +239,9 @@
 	struct lpm_cluster *n;
 
 	if ((cluster->aff_level == affinity_level)
-		|| ((cluster->cpu) && (affinity_level == 0)))
+		|| ((!list_empty(&cluster->cpu)) && (affinity_level == 0)))
 		return cluster;
-	else if (!cluster->cpu) {
+	else if (list_empty(&cluster->cpu)) {
 		n =  list_entry(cluster->child.next, typeof(*n), list);
 		return cluster_aff_match(n, affinity_level);
 	} else
@@ -314,7 +316,7 @@
 
 static int lpm_dying_cpu(unsigned int cpu)
 {
-	struct lpm_cluster *cluster = per_cpu(cpu_cluster, cpu);
+	struct lpm_cluster *cluster = per_cpu(cpu_lpm, cpu)->parent;
 
 	cluster_prepare(cluster, get_cpu_mask(cpu), NR_LPM_LEVELS, false, 0);
 	return 0;
@@ -322,7 +324,7 @@
 
 static int lpm_starting_cpu(unsigned int cpu)
 {
-	struct lpm_cluster *cluster = per_cpu(cpu_cluster, cpu);
+	struct lpm_cluster *cluster = per_cpu(cpu_lpm, cpu)->parent;
 
 	cluster_unprepare(cluster, get_cpu_mask(cpu), NR_LPM_LEVELS, false, 0);
 	return 0;
@@ -376,7 +378,7 @@
 static void clusttimer_cancel(void)
 {
 	int cpu = raw_smp_processor_id();
-	struct lpm_cluster *cluster = per_cpu(cpu_cluster, cpu);
+	struct lpm_cluster *cluster = per_cpu(cpu_lpm, cpu)->parent;
 
 	hrtimer_try_to_cancel(&cluster->histtimer);
 
@@ -1169,12 +1171,11 @@
 	spin_unlock(&cluster->sync_lock);
 }
 
-static inline void cpu_prepare(struct lpm_cluster *cluster, int cpu_index,
+static inline void cpu_prepare(struct lpm_cpu *cpu, int cpu_index,
 				bool from_idle)
 {
-	struct lpm_cpu_level *cpu_level = &cluster->cpu->levels[cpu_index];
-	bool jtag_save_restore =
-			cluster->cpu->levels[cpu_index].jtag_save_restore;
+	struct lpm_cpu_level *cpu_level = &cpu->levels[cpu_index];
+	bool jtag_save_restore = cpu->levels[cpu_index].jtag_save_restore;
 
 	/* Use broadcast timer for aggregating sleep mode within a cluster.
 	 * A broadcast timer could be used in the following scenarios
@@ -1202,12 +1203,11 @@
 		msm_jtag_save_state();
 }
 
-static inline void cpu_unprepare(struct lpm_cluster *cluster, int cpu_index,
+static inline void cpu_unprepare(struct lpm_cpu *cpu, int cpu_index,
 				bool from_idle)
 {
-	struct lpm_cpu_level *cpu_level = &cluster->cpu->levels[cpu_index];
-	bool jtag_save_restore =
-			cluster->cpu->levels[cpu_index].jtag_save_restore;
+	struct lpm_cpu_level *cpu_level = &cpu->levels[cpu_index];
+	bool jtag_save_restore = cpu->levels[cpu_index].jtag_save_restore;
 
 	if (from_idle && cpu_level->use_bc_timer)
 		tick_broadcast_exit();
@@ -1253,13 +1253,12 @@
 	return state_id;
 }
 
-static bool psci_enter_sleep(struct lpm_cluster *cluster, int idx,
-		bool from_idle)
+static bool psci_enter_sleep(struct lpm_cpu *cpu, int idx, bool from_idle)
 {
 	int affinity_level = 0;
-	int state_id = get_cluster_id(cluster, &affinity_level);
+	int state_id = get_cluster_id(cpu->parent, &affinity_level);
 	int power_state =
-		PSCI_POWER_STATE(cluster->cpu->levels[idx].is_reset);
+		PSCI_POWER_STATE(cpu->levels[idx].is_reset);
 	bool success = false;
 	/*
 	 * idx = 0 is the default LPM state
@@ -1273,7 +1272,7 @@
 
 	affinity_level = PSCI_AFFINITY_LEVEL(affinity_level);
 	state_id |= (power_state | affinity_level
-			| cluster->cpu->levels[idx].psci_id);
+			| cpu->levels[idx].psci_id);
 
 	update_debug_pc_event(CPU_ENTER, state_id,
 			0xdeaffeed, 0xdeaffeed, true);
@@ -1288,13 +1287,13 @@
 static int lpm_cpuidle_select(struct cpuidle_driver *drv,
 		struct cpuidle_device *dev)
 {
-	struct lpm_cluster *cluster = per_cpu(cpu_cluster, dev->cpu);
+	struct lpm_cpu *cpu = per_cpu(cpu_lpm, dev->cpu);
 	int idx;
 
-	if (!cluster)
+	if (!cpu)
 		return 0;
 
-	idx = cpu_power_select(dev, cluster->cpu);
+	idx = cpu_power_select(dev, cpu);
 
 	if (idx < 0)
 		return 0;
@@ -1338,18 +1337,18 @@
 static int lpm_cpuidle_enter(struct cpuidle_device *dev,
 		struct cpuidle_driver *drv, int idx)
 {
-	struct lpm_cluster *cluster = per_cpu(cpu_cluster, dev->cpu);
+	struct lpm_cpu *cpu = per_cpu(cpu_lpm, dev->cpu);
 	bool success = true;
 	const struct cpumask *cpumask = get_cpu_mask(dev->cpu);
 	int64_t start_time = ktime_to_ns(ktime_get()), end_time;
 	struct power_params *pwr_params;
 
-	pwr_params = &cluster->cpu->levels[idx].pwr;
+	pwr_params = &cpu->levels[idx].pwr;
 
-	pwr_params = &cluster->cpu->levels[idx].pwr;
+	pwr_params = &cpu->levels[idx].pwr;
 
-	cpu_prepare(cluster, idx, true);
-	cluster_prepare(cluster, cpumask, idx, true, ktime_to_ns(ktime_get()));
+	cpu_prepare(cpu, idx, true);
+	cluster_prepare(cpu->parent, cpumask, idx, true, start_time);
 
 	trace_cpu_idle_enter(idx);
 	lpm_stats_cpu_enter(idx, start_time);
@@ -1357,14 +1356,14 @@
 	if (need_resched() || (idx < 0))
 		goto exit;
 
-	success = psci_enter_sleep(cluster, idx, true);
+	success = psci_enter_sleep(cpu, idx, true);
 
 exit:
 	end_time = ktime_to_ns(ktime_get());
 	lpm_stats_cpu_exit(idx, end_time, success);
 
-	cluster_unprepare(cluster, cpumask, idx, true, end_time);
-	cpu_unprepare(cluster, idx, true);
+	cluster_unprepare(cpu->parent, cpumask, idx, true, end_time);
+	cpu_unprepare(cpu, idx, true);
 	sched_set_cpu_cstate(smp_processor_id(), 0, 0, 0);
 	end_time = ktime_to_ns(ktime_get()) - start_time;
 	do_div(end_time, 1000);
@@ -1434,8 +1433,9 @@
 	int i = 0, ret = 0;
 	unsigned int cpu;
 	struct lpm_cluster *p = NULL;
+	struct lpm_cpu *lpm_cpu;
 
-	if (!cl->cpu) {
+	if (list_empty(&cl->cpu)) {
 		struct lpm_cluster *n;
 
 		list_for_each_entry(n, &cl->child, list) {
@@ -1446,51 +1446,56 @@
 		return ret;
 	}
 
-	cl->drv = kcalloc(1, sizeof(*cl->drv), GFP_KERNEL);
-	if (!cl->drv)
-		return -ENOMEM;
+	list_for_each_entry(lpm_cpu, &cl->cpu, list) {
+		lpm_cpu->drv = kcalloc(1, sizeof(*lpm_cpu->drv), GFP_KERNEL);
+		if (!lpm_cpu->drv)
+			return -ENOMEM;
 
-	cl->drv->name = "msm_idle";
+		lpm_cpu->drv->name = "msm_idle";
 
-	for (i = 0; i < cl->cpu->nlevels; i++) {
-		struct cpuidle_state *st = &cl->drv->states[i];
-		struct lpm_cpu_level *cpu_level = &cl->cpu->levels[i];
+		for (i = 0; i < lpm_cpu->nlevels; i++) {
+			struct cpuidle_state *st = &lpm_cpu->drv->states[i];
+			struct lpm_cpu_level *cpu_level = &lpm_cpu->levels[i];
 
-		snprintf(st->name, CPUIDLE_NAME_LEN, "C%u\n", i);
-		snprintf(st->desc, CPUIDLE_DESC_LEN, cpu_level->name);
-		st->flags = 0;
-		st->exit_latency = cpu_level->pwr.latency_us;
-		st->power_usage = cpu_level->pwr.ss_power;
-		st->target_residency = 0;
-		st->enter = lpm_cpuidle_enter;
-	}
-
-	cl->drv->state_count = cl->cpu->nlevels;
-	cl->drv->safe_state_index = 0;
-	for_each_cpu(cpu, &cl->child_cpus)
-		per_cpu(cpu_cluster, cpu) = cl;
-
-	for_each_possible_cpu(cpu) {
-		if (cpu_online(cpu))
-			continue;
-		p = per_cpu(cpu_cluster, cpu);
-		while (p) {
-			int j;
-
-			spin_lock(&p->sync_lock);
-			cpumask_set_cpu(cpu, &p->num_children_in_sync);
-			for (j = 0; j < p->nlevels; j++)
-				cpumask_copy(&p->levels[j].num_cpu_votes,
-						&p->num_children_in_sync);
-			spin_unlock(&p->sync_lock);
-			p = p->parent;
+			snprintf(st->name, CPUIDLE_NAME_LEN, "C%u\n", i);
+			snprintf(st->desc, CPUIDLE_DESC_LEN, cpu_level->name);
+			st->flags = 0;
+			st->exit_latency = cpu_level->pwr.latency_us;
+			st->power_usage = cpu_level->pwr.ss_power;
+			st->target_residency = 0;
+			st->enter = lpm_cpuidle_enter;
 		}
-	}
-	ret = cpuidle_register_cpu(cl->drv, &cl->child_cpus);
 
-	if (ret) {
-		kfree(cl->drv);
-		return -ENOMEM;
+		lpm_cpu->drv->state_count = lpm_cpu->nlevels;
+		lpm_cpu->drv->safe_state_index = 0;
+		for_each_cpu(cpu, &lpm_cpu->related_cpus)
+			per_cpu(cpu_lpm, cpu) = lpm_cpu;
+
+		for_each_possible_cpu(cpu) {
+			if (cpu_online(cpu))
+				continue;
+			if (per_cpu(cpu_lpm, cpu))
+				p = per_cpu(cpu_lpm, cpu)->parent;
+			while (p) {
+				int j;
+
+				spin_lock(&p->sync_lock);
+				cpumask_set_cpu(cpu, &p->num_children_in_sync);
+				for (j = 0; j < p->nlevels; j++)
+					cpumask_copy(
+						&p->levels[j].num_cpu_votes,
+						&p->num_children_in_sync);
+				spin_unlock(&p->sync_lock);
+				p = p->parent;
+			}
+		}
+		ret = cpuidle_register_cpu(lpm_cpu->drv,
+					&lpm_cpu->related_cpus);
+
+		if (ret) {
+			kfree(lpm_cpu->drv);
+			return -ENOMEM;
+		}
 	}
 	return 0;
 }
@@ -1520,7 +1525,7 @@
 		level_name[i] = cpu->levels[i].name;
 
 	lpm_stats_config_level("cpu", level_name, cpu->nlevels,
-			parent->stats, &parent->child_cpus);
+			parent->stats, &cpu->related_cpus);
 
 	kfree(level_name);
 }
@@ -1529,8 +1534,9 @@
 		struct lpm_cluster *parent)
 {
 	const char **level_name;
-	int i;
 	struct lpm_cluster *child;
+	struct lpm_cpu *cpu;
+	int i;
 
 	if (!cl)
 		return;
@@ -1548,10 +1554,12 @@
 
 	kfree(level_name);
 
-	if (cl->cpu) {
-		register_cpu_lpm_stats(cl->cpu, cl);
-		return;
+	list_for_each_entry(cpu, &cl->cpu, list) {
+		pr_err("%s()\n", __func__);
+		register_cpu_lpm_stats(cpu, cl);
 	}
+	if (!list_empty(&cl->cpu))
+		return;
 
 	list_for_each_entry(child, &cl->child, list)
 		register_cluster_lpm_stats(child, cl);
@@ -1574,8 +1582,8 @@
 static int lpm_suspend_enter(suspend_state_t state)
 {
 	int cpu = raw_smp_processor_id();
-	struct lpm_cluster *cluster = per_cpu(cpu_cluster, cpu);
-	struct lpm_cpu *lpm_cpu = cluster->cpu;
+	struct lpm_cpu *lpm_cpu = per_cpu(cpu_lpm, cpu);
+	struct lpm_cluster *cluster = lpm_cpu->parent;
 	const struct cpumask *cpumask = get_cpu_mask(cpu);
 	int idx;
 
@@ -1588,7 +1596,7 @@
 		pr_err("Failed suspend\n");
 		return 0;
 	}
-	cpu_prepare(cluster, idx, false);
+	cpu_prepare(lpm_cpu, idx, false);
 	cluster_prepare(cluster, cpumask, idx, false, 0);
 	if (idx > 0)
 		update_debug_pc_event(CPU_ENTER, idx, 0xdeaffeed,
@@ -1601,14 +1609,14 @@
 	 * LPMs(XO and Vmin).
 	 */
 
-	psci_enter_sleep(cluster, idx, true);
+	psci_enter_sleep(lpm_cpu, idx, true);
 
 	if (idx > 0)
 		update_debug_pc_event(CPU_EXIT, idx, true, 0xdeaffeed,
 					false);
 
 	cluster_unprepare(cluster, cpumask, idx, false, 0);
-	cpu_unprepare(cluster, idx, false);
+	cpu_unprepare(lpm_cpu, idx, false);
 	return 0;
 }
 
diff --git a/drivers/cpuidle/lpm-levels.h b/drivers/cpuidle/lpm-levels.h
index 9875edd..c9f272e 100644
--- a/drivers/cpuidle/lpm-levels.h
+++ b/drivers/cpuidle/lpm-levels.h
@@ -45,10 +45,13 @@
 };
 
 struct lpm_cpu {
+	struct list_head list;
+	struct cpumask related_cpus;
 	struct lpm_cpu_level levels[NR_LPM_LEVELS];
 	int nlevels;
 	unsigned int psci_mode_shift;
 	unsigned int psci_mode_mask;
+	struct cpuidle_driver *drv;
 	struct lpm_cluster *parent;
 };
 
@@ -104,8 +107,7 @@
 	int min_child_level;
 	int default_level;
 	int last_level;
-	struct lpm_cpu *cpu;
-	struct cpuidle_driver *drv;
+	struct list_head cpu;
 	spinlock_t sync_lock;
 	struct cpumask child_cpus;
 	struct cpumask num_children_in_sync;