Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 168922d3 authored by Mahesh Sivasubramanian's avatar Mahesh Sivasubramanian
Browse files

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: default avatarMahesh Sivasubramanian <msivasub@codeaurora.org>
parent 060f60c4
Loading
Loading
Loading
Loading
+9 −8
Original line number Original line Diff line number Diff line
@@ -28,9 +28,6 @@ Required properties:
	- qcom,default-level: The default low power level that a cluster is
	- qcom,default-level: The default low power level that a cluster is
	programmed. The SPM of the corresponding device is configured at this
	programmed. The SPM of the corresponding device is configured at this
	low power mode by default.
	low power mode by default.
	- qcom,cpu: List of CPU phandles to identify the CPUs associated with
	this cluster. This property is required if and only if the cluster
	node contains a qcom,pm-cpu node.


	qcom,pm-cluster contains qcom,pm-cluster-level nodes which identify
	qcom,pm-cluster contains qcom,pm-cluster-level nodes which identify
	the various low power modes that the cluster can enter. The
	the various low power modes that the cluster can enter. The
@@ -103,9 +100,13 @@ Required properties:
					power collapse (PC)
					power collapse (PC)


[Node bindings for qcom,pm-cpu]
[Node bindings for qcom,pm-cpu]
qcom,pm-cpu contains the low power modes that a cpu could enter. Currently it
qcom,pm-cpu contains the low power modes that a cpu could enter and the CPUs
doesn't have any required properties and is a container for
that share the parameters.It contains the following properties.
qcom,pm-cpu-levels.
	- qcom,cpu: List of CPU phandles to identify the CPUs associated with
	this cluster.
	- qcom,pm-cpu-levels: The different low power modes that a CPU could
	enter. The following section explains the required properties of this
	node.


[Node bindings for qcom,pm-cpu-levels]
[Node bindings for qcom,pm-cpu-levels]
 Required properties:
 Required properties:
@@ -184,7 +185,6 @@ qcom,lpm-levels {
			label = "a53";
			label = "a53";
			qcom,spm-device-names = "l2";
			qcom,spm-device-names = "l2";
			qcom,default-level=<0>;
			qcom,default-level=<0>;
			qcom,cpu = <&CPU0 &CPU1 &CPU2 &CPU3>;


			qcom,pm-cluster-level@0{
			qcom,pm-cluster-level@0{
				reg = <0>;
				reg = <0>;
@@ -210,6 +210,7 @@ qcom,lpm-levels {
			qcom,pm-cpu {
			qcom,pm-cpu {
				#address-cells = <1>;
				#address-cells = <1>;
				#size-cells = <0>;
				#size-cells = <0>;
				qcom,cpu = <&CPU0 &CPU1 &CPU2 &CPU3>;
				qcom,pm-cpu-level@0 {
				qcom,pm-cpu-level@0 {
					reg = <0>;
					reg = <0>;
					qcom,spm-cpu-mode = "wfi";
					qcom,spm-cpu-mode = "wfi";
@@ -255,7 +256,6 @@ qcom,lpm-levels {
			label = "a57";
			label = "a57";
			qcom,spm-device-names = "l2";
			qcom,spm-device-names = "l2";
			qcom,default-level=<0>;
			qcom,default-level=<0>;
			qcom,cpu = <&CPU4 &CPU5 &CPU6 &CPU7>;


			qcom,pm-cluster-level@0{
			qcom,pm-cluster-level@0{
				reg = <0>;
				reg = <0>;
@@ -281,6 +281,7 @@ qcom,lpm-levels {
			qcom,pm-cpu {
			qcom,pm-cpu {
				#address-cells = <1>;
				#address-cells = <1>;
				#size-cells = <0>;
				#size-cells = <0>;
				qcom,cpu = <&CPU4 &CPU5 &CPU6 &CPU7>;
				qcom,pm-cpu-level@0 {
				qcom,pm-cpu-level@0 {
					reg = <0>;
					reg = <0>;
					qcom,spm-cpu-mode = "wfi";
					qcom,spm-cpu-mode = "wfi";
+53 −3
Original line number Original line Diff line number Diff line
@@ -23,8 +23,6 @@
			#size-cells = <0>;
			#size-cells = <0>;
			label = "L3";
			label = "L3";
			qcom,spm-device-names = "L3";
			qcom,spm-device-names = "L3";
			qcom,cpu = <&CPU0 &CPU1 &CPU2 &CPU3 &CPU4 &CPU5 &CPU6
				&CPU7>;
			qcom,psci-mode-shift = <4>;
			qcom,psci-mode-shift = <4>;
			qcom,psci-mode-mask = <0xfff>;
			qcom,psci-mode-mask = <0xfff>;


@@ -86,12 +84,64 @@
				qcom,is-reset;
				qcom,is-reset;
				qcom,notify-rpm;
				qcom,notify-rpm;
			};
			};
			qcom,pm-cpu@0 {
				#address-cells = <1>;
				#size-cells = <0>;
				qcom,psci-mode-shift = <0>;
				qcom,psci-mode-mask = <0xf>;
				qcom,cpu = <&CPU0 &CPU1 &CPU2 &CPU3>;

				qcom,pm-cpu-level@0 { /* C1 */
					reg = <0>;
					qcom,spm-cpu-mode = "wfi";
					qcom,psci-cpu-mode = <0x1>;
					qcom,latency-us = <43>;
					qcom,ss-power = <454>;
					qcom,energy-overhead = <38639>;
					qcom,time-overhead = <83>;
				};

				qcom,pm-cpu-level@1 { /* C2D */
					reg = <1>;
					qcom,psci-cpu-mode = <0x2>;
					qcom,spm-cpu-mode = "ret";
					qcom,latency-us = <86>;
					qcom,ss-power = <449>;
					qcom,energy-overhead = <78456>;
					qcom,time-overhead = <167>;
				};

				qcom,pm-cpu-level@2 {  /* C3 */
					reg = <2>;
					qcom,spm-cpu-mode = "pc";
					qcom,psci-cpu-mode = <0x3>;
					qcom,latency-us = <612>;
					qcom,ss-power = <436>;
					qcom,energy-overhead = <418225>;
					qcom,time-overhead = <885>;
					qcom,is-reset;
					qcom,use-broadcast-timer;
				};

				qcom,pm-cpu-level@3 {  /* C4 */
					reg = <3>;
					qcom,spm-cpu-mode = "rail-pc";
					qcom,psci-cpu-mode = <0x4>;
					qcom,latency-us = <700>;
					qcom,ss-power = <400>;
					qcom,energy-overhead = <428225>;
					qcom,time-overhead = <1000>;
					qcom,is-reset;
					qcom,use-broadcast-timer;
				};
			};


			qcom,pm-cpu {
			qcom,pm-cpu@1 {
				#address-cells = <1>;
				#address-cells = <1>;
				#size-cells = <0>;
				#size-cells = <0>;
				qcom,psci-mode-shift = <0>;
				qcom,psci-mode-shift = <0>;
				qcom,psci-mode-mask = <0xf>;
				qcom,psci-mode-mask = <0xf>;
				qcom,cpu = <&CPU4 &CPU5 &CPU6 &CPU7>;


				qcom,pm-cpu-level@0 { /* C1 */
				qcom,pm-cpu-level@0 { /* C1 */
					reg = <0>;
					reg = <0>;
+123 −106
Original line number Original line Diff line number Diff line
@@ -305,6 +305,7 @@ static int create_cpu_lvl_nodes(struct lpm_cluster *p, struct kobject *parent)
	struct lpm_level_avail *level_list = NULL;
	struct lpm_level_avail *level_list = NULL;
	char cpu_name[20] = {0};
	char cpu_name[20] = {0};
	int ret = 0;
	int ret = 0;
	struct list_head *pos;


	cpu_kobj = devm_kzalloc(&lpm_pdev->dev, sizeof(*cpu_kobj) *
	cpu_kobj = devm_kzalloc(&lpm_pdev->dev, sizeof(*cpu_kobj) *
			cpumask_weight(&p->child_cpus), GFP_KERNEL);
			cpumask_weight(&p->child_cpus), GFP_KERNEL);
@@ -312,16 +313,20 @@ static int create_cpu_lvl_nodes(struct lpm_cluster *p, struct kobject *parent)
		return -ENOMEM;
		return -ENOMEM;


	cpu_idx = 0;
	cpu_idx = 0;
	for_each_cpu(cpu, &p->child_cpus) {
	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);
			snprintf(cpu_name, sizeof(cpu_name), "cpu%d", cpu);
		cpu_kobj[cpu_idx] = kobject_create_and_add(cpu_name, parent);
			cpu_kobj[cpu_idx] = kobject_create_and_add(cpu_name,
					parent);
			if (!cpu_kobj[cpu_idx]) {
			if (!cpu_kobj[cpu_idx]) {
				ret = -ENOMEM;
				ret = -ENOMEM;
				goto release_kobj;
				goto release_kobj;
			}
			}


			level_list = devm_kzalloc(&lpm_pdev->dev,
			level_list = devm_kzalloc(&lpm_pdev->dev,
				p->cpu->nlevels * sizeof(*level_list),
					lpm_cpu->nlevels * sizeof(*level_list),
					GFP_KERNEL);
					GFP_KERNEL);
			if (!level_list) {
			if (!level_list) {
				ret = -ENOMEM;
				ret = -ENOMEM;
@@ -329,15 +334,17 @@ static int create_cpu_lvl_nodes(struct lpm_cluster *p, struct kobject *parent)
			}
			}


			/*
			/*
		 * Skip enable/disable for WFI. cpuidle expects WFI to be
			 * Skip enable/disable for WFI. cpuidle expects WFI to
		 * available at all times.
			 * be available at all times.
			 */
			 */
		for (i = 1; i < p->cpu->nlevels; i++) {
			for (i = 1; i < lpm_cpu->nlevels; i++) {

				level_list[i].latency_us =
			level_list[i].latency_us = p->levels[i].pwr.latency_us;
					p->levels[i].pwr.latency_us;
			ret = create_lvl_avail_nodes(p->cpu->levels[i].name,
				ret = create_lvl_avail_nodes(
					cpu_kobj[cpu_idx], &level_list[i],
						lpm_cpu->levels[i].name,
					(void *)p->cpu, cpu, true);
						cpu_kobj[cpu_idx],
						&level_list[i],
						(void *)lpm_cpu, cpu, true);
				if (ret)
				if (ret)
					goto release_kobj;
					goto release_kobj;
			}
			}
@@ -345,6 +352,7 @@ static int create_cpu_lvl_nodes(struct lpm_cluster *p, struct kobject *parent)
			cpu_level_available[cpu] = level_list;
			cpu_level_available[cpu] = level_list;
			cpu_idx++;
			cpu_idx++;
		}
		}
	}


	return ret;
	return ret;


@@ -384,7 +392,7 @@ int create_cluster_lvl_nodes(struct lpm_cluster *p, struct kobject *kobj)
			return ret;
			return ret;
	}
	}


	if (p->cpu) {
	if (!list_empty(&p->cpu)) {
		ret = create_cpu_lvl_nodes(p, cluster_kobj);
		ret = create_cpu_lvl_nodes(p, cluster_kobj);
		if (ret)
		if (ret)
			return ret;
			return ret;
@@ -619,49 +627,26 @@ static int calculate_residency(struct power_params *base_pwr,
				next_pwr->time_overhead_us : residency;
				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";
	struct device_node *n;

	int ret, i, j;
	ret = of_property_read_u32(node, key, &c->cpu->psci_mode_shift);
	const char *key;
	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;
	}
	for_each_child_of_node(node, n) {
	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);
		ret = parse_cpu_mode(n, l);
		if (ret < 0) {
		if (ret < 0) {
			pr_info("Failed %s\n", l->name);
			pr_info("Failed %s\n", l->name);
			goto failed;
			return ret;
		}
		}


		ret = parse_power_params(n, &l->pwr);
		ret = parse_power_params(n, &l->pwr);
		if (ret)
		if (ret)
			goto failed;
			return ret;


		key = "qcom,use-broadcast-timer";
		key = "qcom,use-broadcast-timer";
		l->use_bc_timer = of_property_read_bool(n, key);
		l->use_bc_timer = of_property_read_bool(n, key);
@@ -676,32 +661,83 @@ static int parse_cpu_levels(struct device_node *node, struct lpm_cluster *c)
		if (ret == -EINVAL)
		if (ret == -EINVAL)
			l->reset_level = LPM_RESET_LVL_NONE;
			l->reset_level = LPM_RESET_LVL_NONE;
		else if (ret)
		else if (ret)
			goto failed;
			return ret;
	}
	}
	for (i = 0; i < c->cpu->nlevels; i++) {
	for (i = 0; i < cpu->nlevels; i++) {
		for (j = 0; j < c->cpu->nlevels; j++) {
		for (j = 0; j < cpu->nlevels; j++) {
			if (i >= j) {
			if (i >= j) {
				c->cpu->levels[i].pwr.residencies[j] = 0;
				cpu->levels[i].pwr.residencies[j] = 0;
				continue;
				continue;
			}
			}


			c->cpu->levels[i].pwr.residencies[j] =
			cpu->levels[i].pwr.residencies[j] =
				calculate_residency(&c->cpu->levels[i].pwr,
				calculate_residency(&cpu->levels[i].pwr,
						&c->cpu->levels[j].pwr);
						&cpu->levels[j].pwr);


			pr_err("%s: idx %d %u\n", __func__, j,
			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;
}

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;
	}
	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;
	return 0;
failed:
failed:
	for (i = 0; i < c->cpu->nlevels; i++) {
	for (i = 0; i < cpu->nlevels; i++) {
		kfree(c->cpu->levels[i].name);
		kfree(cpu->levels[i].name);
		c->cpu->levels[i].name = NULL;
		cpu->levels[i].name = NULL;
	}
	}
	kfree(c->cpu);
	kfree(cpu);
	c->cpu = NULL;
	pr_err("%s(): Failed with error code:%d\n", __func__, ret);
	pr_err("%s(): Failed with error code:%d\n", __func__, ret);
	return ret;
	return ret;
}
}
@@ -709,6 +745,7 @@ static int parse_cpu_levels(struct device_node *node, struct lpm_cluster *c)
void free_cluster_node(struct lpm_cluster *cluster)
void free_cluster_node(struct lpm_cluster *cluster)
{
{
	struct list_head *list;
	struct list_head *list;
	struct lpm_cpu *cpu, *n;
	int i;
	int i;


	list_for_each(list, &cluster->child) {
	list_for_each(list, &cluster->child) {
@@ -719,19 +756,20 @@ void free_cluster_node(struct lpm_cluster *cluster)
		free_cluster_node(n);
		free_cluster_node(n);
	};
	};


	if (cluster->cpu) {
	list_for_each_entry_safe(cpu, n, &cluster->cpu, list) {
		for (i = 0; i < cluster->cpu->nlevels; i++) {
		struct lpm_cpu *cpu = list_entry(list, typeof(*cpu), list);
			kfree(cluster->cpu->levels[i].name);

			cluster->cpu->levels[i].name = NULL;
		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++) {
	for (i = 0; i < cluster->nlevels; i++) {
		kfree(cluster->levels[i].mode);
		kfree(cluster->levels[i].mode);
		cluster->levels[i].mode = NULL;
		cluster->levels[i].mode = NULL;
	}
	}
	kfree(cluster->cpu);
	kfree(cluster->name);
	kfree(cluster->name);
	cluster->cpu = NULL;
	cluster->name = NULL;
	cluster->name = NULL;
	cluster->ndevices = 0;
	cluster->ndevices = 0;
}
}
@@ -761,6 +799,7 @@ struct lpm_cluster *parse_cluster(struct device_node *node,
		goto failed_parse_params;
		goto failed_parse_params;


	INIT_LIST_HEAD(&c->child);
	INIT_LIST_HEAD(&c->child);
	INIT_LIST_HEAD(&c->cpu);
	c->parent = parent;
	c->parent = parent;
	spin_lock_init(&c->sync_lock);
	spin_lock_init(&c->sync_lock);
	c->min_child_level = NR_LPM_LEVELS;
	c->min_child_level = NR_LPM_LEVELS;
@@ -793,34 +832,11 @@ struct lpm_cluster *parse_cluster(struct device_node *node,


		key = "qcom,pm-cpu";
		key = "qcom,pm-cpu";
		if (!of_node_cmp(n->name, key)) {
		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))
			if (parse_cpu_levels(n, c))
				goto failed_parse_cluster;
				goto failed_parse_cluster;


			c->aff_level = 1;
			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 @@ struct lpm_cluster *lpm_of_parse_cluster(struct platform_device *pdev)
void cluster_dt_walkthrough(struct lpm_cluster *cluster)
void cluster_dt_walkthrough(struct lpm_cluster *cluster)
{
{
	struct list_head *list;
	struct list_head *list;
	struct lpm_cpu *cpu;
	int i, j;
	int i, j;
	static int id;
	static int id;
	char str[10] = {0};
	char str[10] = {0};
@@ -890,12 +907,12 @@ void cluster_dt_walkthrough(struct lpm_cluster *cluster)
					&cluster->name[j], &l->mode[i]);
					&cluster->name[j], &l->mode[i]);
	}
	}


	if (cluster->cpu) {
	list_for_each_entry(cpu, &cluster->cpu, list) {
		pr_info("%d\n", __LINE__);
		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,
			pr_info("%s\tCPU mode: %s id:%d\n", str,
					cluster->cpu->levels[j].name,
					cpu->levels[j].name,
					cluster->cpu->levels[j].mode);
					cpu->levels[j].mode);
	}
	}


	id++;
	id++;
+97 −89
Original line number Original line Diff line number Diff line
@@ -104,7 +104,7 @@ struct lpm_history {


static DEFINE_PER_CPU(struct lpm_history, hist);
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 bool suspend_in_progress;
static struct hrtimer lpm_hrtimer;
static struct hrtimer lpm_hrtimer;
static struct hrtimer histtimer;
static struct hrtimer histtimer;
@@ -207,7 +207,7 @@ static uint32_t least_cpu_latency(struct list_head *child,
	struct power_params *pwr_params;
	struct power_params *pwr_params;
	struct lpm_cpu *cpu;
	struct lpm_cpu *cpu;
	struct lpm_cluster *n;
	struct lpm_cluster *n;
	uint32_t latency = 0;
	uint32_t lat = 0;
	int i;
	int i;


	list_for_each(list, child) {
	list_for_each(list, child) {
@@ -216,19 +216,21 @@ static uint32_t least_cpu_latency(struct list_head *child,
			if (strcmp(lat_level->level_name, n->cluster_name))
			if (strcmp(lat_level->level_name, n->cluster_name))
				continue;
				continue;
		}
		}
		cpu = n->cpu;
		list_for_each_entry(cpu, &n->cpu, list) {
			for (i = 0; i < cpu->nlevels; i++) {
			for (i = 0; i < cpu->nlevels; i++) {
				level = &cpu->levels[i];
				level = &cpu->levels[i];
				pwr_params = &level->pwr;
				pwr_params = &level->pwr;
			if (lat_level->reset_level == level->reset_level) {
				if (lat_level->reset_level
				if ((latency > pwr_params->latency_us)
						== level->reset_level) {
							|| (!latency))
					if ((lat > pwr_params->latency_us)
					latency = pwr_params->latency_us;
							|| (!lat))
						lat = pwr_params->latency_us;
					break;
					break;
				}
				}
			}
			}
		}
		}
	return latency;
	}
	return lat;
}
}


static struct lpm_cluster *cluster_aff_match(struct lpm_cluster *cluster,
static struct lpm_cluster *cluster_aff_match(struct lpm_cluster *cluster,
@@ -237,9 +239,9 @@ static struct lpm_cluster *cluster_aff_match(struct lpm_cluster *cluster,
	struct lpm_cluster *n;
	struct lpm_cluster *n;


	if ((cluster->aff_level == affinity_level)
	if ((cluster->aff_level == affinity_level)
		|| ((cluster->cpu) && (affinity_level == 0)))
		|| ((!list_empty(&cluster->cpu)) && (affinity_level == 0)))
		return cluster;
		return cluster;
	else if (!cluster->cpu) {
	else if (list_empty(&cluster->cpu)) {
		n =  list_entry(cluster->child.next, typeof(*n), list);
		n =  list_entry(cluster->child.next, typeof(*n), list);
		return cluster_aff_match(n, affinity_level);
		return cluster_aff_match(n, affinity_level);
	} else
	} else
@@ -314,7 +316,7 @@ static void update_debug_pc_event(enum debug_event event, uint32_t arg1,


static int lpm_dying_cpu(unsigned int cpu)
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);
	cluster_prepare(cluster, get_cpu_mask(cpu), NR_LPM_LEVELS, false, 0);
	return 0;
	return 0;
@@ -322,7 +324,7 @@ static int lpm_dying_cpu(unsigned int cpu)


static int lpm_starting_cpu(unsigned int cpu)
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);
	cluster_unprepare(cluster, get_cpu_mask(cpu), NR_LPM_LEVELS, false, 0);
	return 0;
	return 0;
@@ -376,7 +378,7 @@ static void cluster_timer_init(struct lpm_cluster *cluster)
static void clusttimer_cancel(void)
static void clusttimer_cancel(void)
{
{
	int cpu = raw_smp_processor_id();
	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);
	hrtimer_try_to_cancel(&cluster->histtimer);


@@ -1169,12 +1171,11 @@ static void cluster_unprepare(struct lpm_cluster *cluster,
	spin_unlock(&cluster->sync_lock);
	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)
				bool from_idle)
{
{
	struct lpm_cpu_level *cpu_level = &cluster->cpu->levels[cpu_index];
	struct lpm_cpu_level *cpu_level = &cpu->levels[cpu_index];
	bool jtag_save_restore =
	bool jtag_save_restore = cpu->levels[cpu_index].jtag_save_restore;
			cluster->cpu->levels[cpu_index].jtag_save_restore;


	/* Use broadcast timer for aggregating sleep mode within a cluster.
	/* Use broadcast timer for aggregating sleep mode within a cluster.
	 * A broadcast timer could be used in the following scenarios
	 * A broadcast timer could be used in the following scenarios
@@ -1202,12 +1203,11 @@ static inline void cpu_prepare(struct lpm_cluster *cluster, int cpu_index,
		msm_jtag_save_state();
		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)
				bool from_idle)
{
{
	struct lpm_cpu_level *cpu_level = &cluster->cpu->levels[cpu_index];
	struct lpm_cpu_level *cpu_level = &cpu->levels[cpu_index];
	bool jtag_save_restore =
	bool jtag_save_restore = cpu->levels[cpu_index].jtag_save_restore;
			cluster->cpu->levels[cpu_index].jtag_save_restore;


	if (from_idle && cpu_level->use_bc_timer)
	if (from_idle && cpu_level->use_bc_timer)
		tick_broadcast_exit();
		tick_broadcast_exit();
@@ -1253,13 +1253,12 @@ int get_cluster_id(struct lpm_cluster *cluster, int *aff_lvl)
	return state_id;
	return state_id;
}
}


static bool psci_enter_sleep(struct lpm_cluster *cluster, int idx,
static bool psci_enter_sleep(struct lpm_cpu *cpu, int idx, bool from_idle)
		bool from_idle)
{
{
	int affinity_level = 0;
	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 =
	int power_state =
		PSCI_POWER_STATE(cluster->cpu->levels[idx].is_reset);
		PSCI_POWER_STATE(cpu->levels[idx].is_reset);
	bool success = false;
	bool success = false;
	/*
	/*
	 * idx = 0 is the default LPM state
	 * idx = 0 is the default LPM state
@@ -1273,7 +1272,7 @@ static bool psci_enter_sleep(struct lpm_cluster *cluster, int idx,


	affinity_level = PSCI_AFFINITY_LEVEL(affinity_level);
	affinity_level = PSCI_AFFINITY_LEVEL(affinity_level);
	state_id |= (power_state | 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,
	update_debug_pc_event(CPU_ENTER, state_id,
			0xdeaffeed, 0xdeaffeed, true);
			0xdeaffeed, 0xdeaffeed, true);
@@ -1288,13 +1287,13 @@ static bool psci_enter_sleep(struct lpm_cluster *cluster, int idx,
static int lpm_cpuidle_select(struct cpuidle_driver *drv,
static int lpm_cpuidle_select(struct cpuidle_driver *drv,
		struct cpuidle_device *dev)
		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;
	int idx;


	if (!cluster)
	if (!cpu)
		return 0;
		return 0;


	idx = cpu_power_select(dev, cluster->cpu);
	idx = cpu_power_select(dev, cpu);


	if (idx < 0)
	if (idx < 0)
		return 0;
		return 0;
@@ -1338,18 +1337,18 @@ static void update_history(struct cpuidle_device *dev, int idx)
static int lpm_cpuidle_enter(struct cpuidle_device *dev,
static int lpm_cpuidle_enter(struct cpuidle_device *dev,
		struct cpuidle_driver *drv, int idx)
		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;
	bool success = true;
	const struct cpumask *cpumask = get_cpu_mask(dev->cpu);
	const struct cpumask *cpumask = get_cpu_mask(dev->cpu);
	int64_t start_time = ktime_to_ns(ktime_get()), end_time;
	int64_t start_time = ktime_to_ns(ktime_get()), end_time;
	struct power_params *pwr_params;
	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);
	cpu_prepare(cpu, idx, true);
	cluster_prepare(cluster, cpumask, idx, true, ktime_to_ns(ktime_get()));
	cluster_prepare(cpu->parent, cpumask, idx, true, start_time);


	trace_cpu_idle_enter(idx);
	trace_cpu_idle_enter(idx);
	lpm_stats_cpu_enter(idx, start_time);
	lpm_stats_cpu_enter(idx, start_time);
@@ -1357,14 +1356,14 @@ static int lpm_cpuidle_enter(struct cpuidle_device *dev,
	if (need_resched() || (idx < 0))
	if (need_resched() || (idx < 0))
		goto exit;
		goto exit;


	success = psci_enter_sleep(cluster, idx, true);
	success = psci_enter_sleep(cpu, idx, true);


exit:
exit:
	end_time = ktime_to_ns(ktime_get());
	end_time = ktime_to_ns(ktime_get());
	lpm_stats_cpu_exit(idx, end_time, success);
	lpm_stats_cpu_exit(idx, end_time, success);


	cluster_unprepare(cluster, cpumask, idx, true, end_time);
	cluster_unprepare(cpu->parent, cpumask, idx, true, end_time);
	cpu_unprepare(cluster, idx, true);
	cpu_unprepare(cpu, idx, true);
	sched_set_cpu_cstate(smp_processor_id(), 0, 0, 0);
	sched_set_cpu_cstate(smp_processor_id(), 0, 0, 0);
	end_time = ktime_to_ns(ktime_get()) - start_time;
	end_time = ktime_to_ns(ktime_get()) - start_time;
	do_div(end_time, 1000);
	do_div(end_time, 1000);
@@ -1434,8 +1433,9 @@ static int cluster_cpuidle_register(struct lpm_cluster *cl)
	int i = 0, ret = 0;
	int i = 0, ret = 0;
	unsigned int cpu;
	unsigned int cpu;
	struct lpm_cluster *p = NULL;
	struct lpm_cluster *p = NULL;
	struct lpm_cpu *lpm_cpu;


	if (!cl->cpu) {
	if (list_empty(&cl->cpu)) {
		struct lpm_cluster *n;
		struct lpm_cluster *n;


		list_for_each_entry(n, &cl->child, list) {
		list_for_each_entry(n, &cl->child, list) {
@@ -1446,15 +1446,16 @@ static int cluster_cpuidle_register(struct lpm_cluster *cl)
		return ret;
		return ret;
	}
	}


	cl->drv = kcalloc(1, sizeof(*cl->drv), GFP_KERNEL);
	list_for_each_entry(lpm_cpu, &cl->cpu, list) {
	if (!cl->drv)
		lpm_cpu->drv = kcalloc(1, sizeof(*lpm_cpu->drv), GFP_KERNEL);
		if (!lpm_cpu->drv)
			return -ENOMEM;
			return -ENOMEM;


	cl->drv->name = "msm_idle";
		lpm_cpu->drv->name = "msm_idle";


	for (i = 0; i < cl->cpu->nlevels; i++) {
		for (i = 0; i < lpm_cpu->nlevels; i++) {
		struct cpuidle_state *st = &cl->drv->states[i];
			struct cpuidle_state *st = &lpm_cpu->drv->states[i];
		struct lpm_cpu_level *cpu_level = &cl->cpu->levels[i];
			struct lpm_cpu_level *cpu_level = &lpm_cpu->levels[i];


			snprintf(st->name, CPUIDLE_NAME_LEN, "C%u\n", i);
			snprintf(st->name, CPUIDLE_NAME_LEN, "C%u\n", i);
			snprintf(st->desc, CPUIDLE_DESC_LEN, cpu_level->name);
			snprintf(st->desc, CPUIDLE_DESC_LEN, cpu_level->name);
@@ -1465,33 +1466,37 @@ static int cluster_cpuidle_register(struct lpm_cluster *cl)
			st->enter = lpm_cpuidle_enter;
			st->enter = lpm_cpuidle_enter;
		}
		}


	cl->drv->state_count = cl->cpu->nlevels;
		lpm_cpu->drv->state_count = lpm_cpu->nlevels;
	cl->drv->safe_state_index = 0;
		lpm_cpu->drv->safe_state_index = 0;
	for_each_cpu(cpu, &cl->child_cpus)
		for_each_cpu(cpu, &lpm_cpu->related_cpus)
		per_cpu(cpu_cluster, cpu) = cl;
			per_cpu(cpu_lpm, cpu) = lpm_cpu;


		for_each_possible_cpu(cpu) {
		for_each_possible_cpu(cpu) {
			if (cpu_online(cpu))
			if (cpu_online(cpu))
				continue;
				continue;
		p = per_cpu(cpu_cluster, cpu);
			if (per_cpu(cpu_lpm, cpu))
				p = per_cpu(cpu_lpm, cpu)->parent;
			while (p) {
			while (p) {
				int j;
				int j;


				spin_lock(&p->sync_lock);
				spin_lock(&p->sync_lock);
				cpumask_set_cpu(cpu, &p->num_children_in_sync);
				cpumask_set_cpu(cpu, &p->num_children_in_sync);
				for (j = 0; j < p->nlevels; j++)
				for (j = 0; j < p->nlevels; j++)
				cpumask_copy(&p->levels[j].num_cpu_votes,
					cpumask_copy(
						&p->levels[j].num_cpu_votes,
						&p->num_children_in_sync);
						&p->num_children_in_sync);
				spin_unlock(&p->sync_lock);
				spin_unlock(&p->sync_lock);
				p = p->parent;
				p = p->parent;
			}
			}
		}
		}
	ret = cpuidle_register_cpu(cl->drv, &cl->child_cpus);
		ret = cpuidle_register_cpu(lpm_cpu->drv,
					&lpm_cpu->related_cpus);


		if (ret) {
		if (ret) {
		kfree(cl->drv);
			kfree(lpm_cpu->drv);
			return -ENOMEM;
			return -ENOMEM;
		}
		}
	}
	return 0;
	return 0;
}
}


@@ -1520,7 +1525,7 @@ static void register_cpu_lpm_stats(struct lpm_cpu *cpu,
		level_name[i] = cpu->levels[i].name;
		level_name[i] = cpu->levels[i].name;


	lpm_stats_config_level("cpu", level_name, cpu->nlevels,
	lpm_stats_config_level("cpu", level_name, cpu->nlevels,
			parent->stats, &parent->child_cpus);
			parent->stats, &cpu->related_cpus);


	kfree(level_name);
	kfree(level_name);
}
}
@@ -1529,8 +1534,9 @@ static void register_cluster_lpm_stats(struct lpm_cluster *cl,
		struct lpm_cluster *parent)
		struct lpm_cluster *parent)
{
{
	const char **level_name;
	const char **level_name;
	int i;
	struct lpm_cluster *child;
	struct lpm_cluster *child;
	struct lpm_cpu *cpu;
	int i;


	if (!cl)
	if (!cl)
		return;
		return;
@@ -1548,10 +1554,12 @@ static void register_cluster_lpm_stats(struct lpm_cluster *cl,


	kfree(level_name);
	kfree(level_name);


	if (cl->cpu) {
	list_for_each_entry(cpu, &cl->cpu, list) {
		register_cpu_lpm_stats(cl->cpu, cl);
		pr_err("%s()\n", __func__);
		return;
		register_cpu_lpm_stats(cpu, cl);
	}
	}
	if (!list_empty(&cl->cpu))
		return;


	list_for_each_entry(child, &cl->child, list)
	list_for_each_entry(child, &cl->child, list)
		register_cluster_lpm_stats(child, cl);
		register_cluster_lpm_stats(child, cl);
@@ -1574,8 +1582,8 @@ static void lpm_suspend_wake(void)
static int lpm_suspend_enter(suspend_state_t state)
static int lpm_suspend_enter(suspend_state_t state)
{
{
	int cpu = raw_smp_processor_id();
	int cpu = raw_smp_processor_id();
	struct lpm_cluster *cluster = per_cpu(cpu_cluster, cpu);
	struct lpm_cpu *lpm_cpu = per_cpu(cpu_lpm, cpu);
	struct lpm_cpu *lpm_cpu = cluster->cpu;
	struct lpm_cluster *cluster = lpm_cpu->parent;
	const struct cpumask *cpumask = get_cpu_mask(cpu);
	const struct cpumask *cpumask = get_cpu_mask(cpu);
	int idx;
	int idx;


@@ -1588,7 +1596,7 @@ static int lpm_suspend_enter(suspend_state_t state)
		pr_err("Failed suspend\n");
		pr_err("Failed suspend\n");
		return 0;
		return 0;
	}
	}
	cpu_prepare(cluster, idx, false);
	cpu_prepare(lpm_cpu, idx, false);
	cluster_prepare(cluster, cpumask, idx, false, 0);
	cluster_prepare(cluster, cpumask, idx, false, 0);
	if (idx > 0)
	if (idx > 0)
		update_debug_pc_event(CPU_ENTER, idx, 0xdeaffeed,
		update_debug_pc_event(CPU_ENTER, idx, 0xdeaffeed,
@@ -1601,14 +1609,14 @@ static int lpm_suspend_enter(suspend_state_t state)
	 * LPMs(XO and Vmin).
	 * LPMs(XO and Vmin).
	 */
	 */


	psci_enter_sleep(cluster, idx, true);
	psci_enter_sleep(lpm_cpu, idx, true);


	if (idx > 0)
	if (idx > 0)
		update_debug_pc_event(CPU_EXIT, idx, true, 0xdeaffeed,
		update_debug_pc_event(CPU_EXIT, idx, true, 0xdeaffeed,
					false);
					false);


	cluster_unprepare(cluster, cpumask, idx, false, 0);
	cluster_unprepare(cluster, cpumask, idx, false, 0);
	cpu_unprepare(cluster, idx, false);
	cpu_unprepare(lpm_cpu, idx, false);
	return 0;
	return 0;
}
}


+4 −2
Original line number Original line Diff line number Diff line
@@ -45,10 +45,13 @@ struct lpm_cpu_level {
};
};


struct lpm_cpu {
struct lpm_cpu {
	struct list_head list;
	struct cpumask related_cpus;
	struct lpm_cpu_level levels[NR_LPM_LEVELS];
	struct lpm_cpu_level levels[NR_LPM_LEVELS];
	int nlevels;
	int nlevels;
	unsigned int psci_mode_shift;
	unsigned int psci_mode_shift;
	unsigned int psci_mode_mask;
	unsigned int psci_mode_mask;
	struct cpuidle_driver *drv;
	struct lpm_cluster *parent;
	struct lpm_cluster *parent;
};
};


@@ -104,8 +107,7 @@ struct lpm_cluster {
	int min_child_level;
	int min_child_level;
	int default_level;
	int default_level;
	int last_level;
	int last_level;
	struct lpm_cpu *cpu;
	struct list_head cpu;
	struct cpuidle_driver *drv;
	spinlock_t sync_lock;
	spinlock_t sync_lock;
	struct cpumask child_cpus;
	struct cpumask child_cpus;
	struct cpumask num_children_in_sync;
	struct cpumask num_children_in_sync;