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

Commit 7cefdbe2 authored by Archana Sathyakumar's avatar Archana Sathyakumar
Browse files

msm-core: Supplement scheduler core selection using power estimates



The msm-core driver provides per-core temperature and power for every
P-state (frequency/capacity) to the scheduler.
The objective of the driver is to compute the power consumed
at every P-state so as to make power efficient scheduling decisions.
     Dynamic power = fn (V, f)
     where V is voltage at the frequency f

The power computation and temperature read for all cores happen at
regular intervals (deferrable) unless there is a change in temperature
or frequency.

Change-Id: I2737145955d23e0dfbfabc134ce3e1e73c3e36a1
Signed-off-by: default avatarArchana Sathyakumar <asathyak@codeaurora.org>
parent bacf292c
Loading
Loading
Loading
Loading
+77 −0
Original line number Diff line number Diff line
MSM Core Energy Aware driver

The Energy Aware driver provides per core power and temperature
information to the scheduler for it to make more power efficient
scheduling decision.

The required nodes for the Energy-aware driver are:

- compatible:    "qcom,apss-core-ea"

Required properties:
- qcom,core-mapping: Parent node that lists characteristics of
                 the cpus. Requires the child node for each
                 cpu.

Optional properties:
- qcom,low-hyst-temp: Degrees C below which the power numbers
                 need to be recomputed for the cores and reset
                 the threshold. If this is not present, the default
                 value is 10C.
- qcom,high-hyst-temp: Degrees C above which the power numbers
                 need to be recomputed for the cores and reset
                 the threshold. If this property is not present,
                 the default value is 5C.
- qcom,polling-interval: Interval for which the power numbers
                 need to be recomputed for the cores if there
                 is no change in threshold. If this property is not
                 present, the power is recalculated only on
                 temperature threshold notifications.

[Second level nodes]
Required properties to define per core characteristics:
- qcom,cpu-name: CPU phandle to read the mpidr value
- qcom,apc-rail: CPU rail phandle to get frequency and voltage
                 information.

Optional properties to define per core characteristics:
- qcom,sensor:  Sensor phandle to map a particular sensor to the core.
                If this property is not present, then the core is assumed
                to be at 40C for all the power estimations. No sensor
                threshold is set.

Example

qcom,msm-core {
	compatible = "qcom,apss-core-ea";
	qcom,low-hyst-temp = <10>;
	qcom,high-hyst-temp = <5>;
	qcom,polling-interval = <50>;

	qcom,core-mapping {
		qcom,cpu0-chars {
			qcom,sensor = <&sensor_information0>;
			qcom,cpu-name = <&CPU0>;
			qcom,apc-rail = <&apc0_vreg_corner>;
		};

		qcom,cpu1-chars {
			qcom,sensor = <&sensor_information1>;
			qcom,cpu-name = <&CPU1>;
			qcom,apc-rail = <&apc0_vreg_corner>;
		};

		qcom,cpu2-chars {
			qcom,sensor = <&sensor_information2>;
			qcom,cpu-name = <&CPU2>;
			qcom,apc-rail = <&apc0_vreg_corner>;
		};

		qcom,cpu3-chars {
			qcom,sensor = <&sensor_information3>;
			qcom,cpu-name = <&CPU3>;
			qcom,apc-rail = <&apc0_vreg_corner>;
		};
	};
};
+7 −0
Original line number Diff line number Diff line
@@ -14,6 +14,13 @@ config MSM_NOPM
	  This enables bare minimum support of power management at platform level.
	  i.e WFI

config APSS_CORE_EA
	depends on CPU_FREQ && PM_OPP
	bool "Qualcomm Technology Inc specific power aware driver"
	help
	  Platform specific power aware driver to provide power
	  and temperature information to the scheduler.

if MSM_PM
menuconfig MSM_IDLE_STATS
	bool "Collect idle statistics"
+1 −0
Original line number Diff line number Diff line
@@ -3,3 +3,4 @@ obj-$(CONFIG_MSM_IDLE_STATS) += pm-stats.o
obj-$(CONFIG_MSM_IDLE_STATS)	+= lpm-stats.o
obj-$(CONFIG_MSM_NOPM)		+= no-pm.o
obj-$(CONFIG_PM)		+= pm-boot.o
obj-$(CONFIG_APSS_CORE_EA)	+= msm-core.o
+832 −0
Original line number Diff line number Diff line
/* Copyright (c) 2014, 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/cpu.h>
#include <linux/cpufreq.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kthread.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/msm-core-interface.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/pm_opp.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <linux/slab.h>
#include <linux/thermal.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <asm/smp_plat.h>
#include <stdbool.h>
#define CREATE_TRACE_POINTS
#include <trace/events/trace_msm_core.h>

#define TEMP_BASE_POINT 35
#define TEMP_MAX_POINT 95
#define CPU_BIT_MASK(cpu) BIT(cpu)
#define DEFAULT_TEMP 40
#define DEFAULT_LOW_HYST_TEMP 10
#define DEFAULT_HIGH_HYST_TEMP 5
#define CLUSTER_OFFSET_FOR_MPIDR 8
#define MAX_CORES_PER_CLUSTER 4
#define MAX_NUM_OF_CLUSTERS 2

#define ALLOCATE_2D_ARRAY(type)\
static type **allocate_2d_array_##type(int idx)\
{\
	int i;\
	type **ptr = NULL;\
	if (!idx) \
		return ERR_PTR(-EINVAL);\
	ptr = kzalloc(sizeof(*ptr) * TEMP_DATA_POINTS, \
				GFP_KERNEL);\
	if (!ptr) { \
		return ERR_PTR(-ENOMEM); \
	} \
	for (i = 0; i < TEMP_DATA_POINTS; i++) { \
		ptr[i] = kzalloc(sizeof(*ptr[i]) * \
					idx, GFP_KERNEL);\
		if (!ptr[i]) {\
			goto done;\
		} \
	} \
	return ptr;\
done:\
	for (i = 0; i < TEMP_DATA_POINTS; i++) \
		kfree(ptr[i]);\
	kfree(ptr);\
	return ERR_PTR(-ENOMEM);\
}

struct cpu_activity_info {
	int cpu;
	int mpidr;
	long temp;
	int sensor_id;
	struct sensor_threshold hi_threshold;
	struct sensor_threshold low_threshold;
	struct device_node *apc_node;
	struct cpu_static_info *sp;
};

struct cpu_static_info {
	uint32_t **power;
	cpumask_t mask;
	struct cpufreq_frequency_table *table;
	uint32_t *voltage;
	uint32_t num_of_freqs;
};

static struct delayed_work sampling_work;
static struct completion sampling_completion;
static struct task_struct *sampling_task;
static int low_hyst_temp;
static int high_hyst_temp;
static struct cpu_activity_info activity[NR_CPUS];
DEFINE_PER_CPU(struct cpu_pstate_pwr *, ptable);
static struct cpu_pwr_stats cpu_stats[NR_CPUS];
ALLOCATE_2D_ARRAY(uint32_t);

static int poll_ms;
module_param_named(polling_interval, poll_ms, int,
		S_IRUGO | S_IWUSR | S_IWGRP);

static int disabled;
module_param_named(disabled, disabled, int,
		S_IRUGO | S_IWUSR | S_IWGRP);
/*
 * Cannot be called from an interrupt context
 */
static void set_and_activate_threshold(uint32_t sensor_id,
	struct sensor_threshold *threshold)
{
	if (sensor_set_trip(sensor_id, threshold)) {
		pr_err("%s: Error in setting trip %d\n",
			KBUILD_MODNAME, threshold->trip);
		return;
	}

	if (sensor_activate_trip(sensor_id, threshold, true)) {
		sensor_cancel_trip(sensor_id, threshold);
		pr_err("%s: Error in enabling trip %d\n",
			KBUILD_MODNAME, threshold->trip);
		return;
	}
}

static void set_threshold(struct cpu_activity_info *cpu_node)
{
	if (cpu_node->sensor_id < 0)
		return;

	set_and_activate_threshold(cpu_node->sensor_id,
		&cpu_node->hi_threshold);

	set_and_activate_threshold(cpu_node->sensor_id,
		&cpu_node->low_threshold);
}

static void samplequeue_handle(struct work_struct *work)
{
	complete(&sampling_completion);
}

/* May be called from an interrupt context */
static void core_temp_notify(enum thermal_trip_type type,
		int temp, void *data)
{
	struct cpu_activity_info *cpu_node =
		(struct cpu_activity_info *) data;

	trace_temp_notification(cpu_node->sensor_id,
		type, temp, cpu_node->temp);

	cpu_node->temp = temp;

	complete(&sampling_completion);
}

static void repopulate_stats(int cpu)
{
	int i;
	struct cpu_activity_info *cpu_node = &activity[cpu];
	int temp_point;
	struct cpu_pstate_pwr *pt =  per_cpu(ptable, cpu);

	if (cpu_node->temp < TEMP_BASE_POINT)
		temp_point = 0;
	else if (cpu_node->temp > TEMP_MAX_POINT)
		temp_point = TEMP_DATA_POINTS - 1;
	else
		temp_point = (cpu_node->temp - TEMP_BASE_POINT) / 5;

	cpu_stats[cpu].temp = cpu_node->temp;
	for (i = 0; i < cpu_node->sp->num_of_freqs; i++)
		pt[i].power = cpu_node->sp->power[temp_point][i];

	trace_cpu_stats(cpu, cpu_stats[cpu].temp, pt[0].power,
			pt[cpu_node->sp->num_of_freqs-1].power);
};

void trigger_cpu_pwr_stats_calc(void)
{
	int cpu;
	static long prev_temp[NR_CPUS];
	static DEFINE_SPINLOCK(update_lock);
	struct cpu_activity_info *cpu_node;

	if (disabled)
		return;

	spin_lock(&update_lock);

	for_each_online_cpu(cpu) {
		cpu_node = &activity[cpu];
		if (cpu_node->sensor_id < 0)
			continue;

		if (cpu_node->temp == prev_temp[cpu])
			sensor_get_temp(cpu_node->sensor_id, &cpu_node->temp);
		prev_temp[cpu] = cpu_node->temp;

		repopulate_stats(cpu);
	}
	spin_unlock(&update_lock);
}
EXPORT_SYMBOL(trigger_cpu_pwr_stats_calc);

static __ref int do_sampling(void *data)
{
	int cpu;
	struct cpu_activity_info *cpu_node;
	static int prev_temp[NR_CPUS];

	while (!kthread_should_stop()) {
		wait_for_completion(&sampling_completion);
		cancel_delayed_work(&sampling_work);
		trigger_cpu_pwr_stats_calc();

		for_each_online_cpu(cpu) {
			cpu_node = &activity[cpu];
			if (prev_temp[cpu] == cpu_node->temp)
				continue;

			prev_temp[cpu] = cpu_node->temp;
			cpu_node->low_threshold.temp = cpu_node->temp
							- low_hyst_temp;
			cpu_node->hi_threshold.temp = cpu_node->temp
							+ high_hyst_temp;
			set_threshold(cpu_node);
			trace_temp_threshold(cpu, cpu_node->temp,
				cpu_node->hi_threshold.temp,
				cpu_node->low_threshold.temp);
		}
		if (!poll_ms)
			continue;

		schedule_delayed_work(&sampling_work,
			msecs_to_jiffies(poll_ms));
	}
	return 0;
}

static void clear_static_power(struct cpu_static_info *sp)
{
	int i;

	if (!sp)
		return;

	if (cpumask_first(&sp->mask) < num_possible_cpus())
		return;

	for (i = 0; i < TEMP_DATA_POINTS; i++)
		kfree(sp->power[i]);
	kfree(sp->power);
	kfree(sp);
}

static int update_userspace_power(struct sched_params __user *argp)
{
	int i;
	int ret;
	int cpu;
	struct cpu_activity_info *node;
	struct cpu_static_info *sp, *clear_sp;
	int mpidr = (argp->cluster << 8);
	int cpumask = argp->cpumask;

	pr_debug("cpumask %d, cluster: %d\n", argp->cpumask, argp->cluster);
	for (i = 0; i < MAX_CORES_PER_CLUSTER; i++, cpumask >>= 1) {
		if (!(cpumask & 0x01))
			continue;

		mpidr |= i;
		for_each_possible_cpu(cpu) {
			if (cpu_logical_map(cpu) == mpidr)
				break;
		}
	}

	if (cpu >= num_possible_cpus())
		return -EINVAL;

	node = &activity[cpu];
	/* Allocate new memory to copy cpumask specific power
	 * information.
	 */
	sp = kzalloc(sizeof(*sp), GFP_KERNEL);
	if (!sp)
		return -ENOMEM;


	sp->power = allocate_2d_array_uint32_t(node->sp->num_of_freqs);
	if (IS_ERR_OR_NULL(sp->power)) {
		ret = PTR_ERR(sp->power);
		kfree(sp);
		return ret;
	}
	sp->num_of_freqs = node->sp->num_of_freqs;
	sp->voltage = node->sp->voltage;
	sp->table = node->sp->table;

	for (i = 0; i < TEMP_DATA_POINTS; i++) {
		ret = copy_from_user(sp->power[i], &argp->power[i][0],
			sizeof(sp->power[i][0]) * node->sp->num_of_freqs);
		if (ret)
			goto failed;
	}

	/* Copy the same power values for all the cpus in the cpumask
	 * argp->cpumask within the cluster (argp->cluster)
	 */
	cpumask = argp->cpumask;
	for (i = 0; i < MAX_CORES_PER_CLUSTER; i++, cpumask >>= 1) {
		if (!(cpumask & 0x01))
			continue;
		mpidr = (argp->cluster << CLUSTER_OFFSET_FOR_MPIDR);
		mpidr |= i;
		for_each_possible_cpu(cpu) {
			if (!(cpu_logical_map(cpu) == mpidr))
				continue;

			node = &activity[cpu];
			clear_sp = node->sp;
			node->sp = sp;
			cpumask_set_cpu(cpu, &sp->mask);
			if (clear_sp) {
				cpumask_clear_cpu(cpu, &clear_sp->mask);
				clear_static_power(clear_sp);
			}
		}
	}

	for_each_possible_cpu(cpu)
		repopulate_stats(cpu);
	return 0;

failed:
	for (i = 0; i < TEMP_DATA_POINTS; i++)
		kfree(sp->power[i]);
	kfree(sp->power);
	kfree(sp);
	return ret;
}

static long msm_core_ioctl(struct file *file, unsigned int cmd,
		unsigned long arg)
{
	long ret = 0;
	struct cpu_activity_info *node = NULL;
	struct sched_params __user *argp = (struct sched_params __user *)arg;
	int i, cpu = num_possible_cpus();
	int mpidr = (argp->cluster << (MAX_CORES_PER_CLUSTER *
			MAX_NUM_OF_CLUSTERS));
	int cpumask = argp->cpumask;

	if (!argp)
		return -EINVAL;

	switch (cmd) {
	case EA_LEAKAGE:
		ret = update_userspace_power(argp);
		if (ret)
			pr_err("Userspace power update failed with %ld\n", ret);
		break;
	case EA_VOLT:
		for (i = 0; i < MAX_CORES_PER_CLUSTER; i++, cpumask >>= 1) {
			if (!(cpumask & 0x01))
				continue;

			mpidr |= i;
			for_each_possible_cpu(cpu) {
				if (cpu_logical_map(cpu) == mpidr)
					break;
			}
		}
		if (cpu >= num_possible_cpus())
			break;
		node = &activity[cpu];
		ret = copy_to_user((void __user *)&argp->voltage[0],
				node->sp->voltage,
				sizeof(uint32_t) * node->sp->num_of_freqs);
		if (ret)
			break;
		for (i = 0; i < node->sp->num_of_freqs; i++) {
			ret = copy_to_user((void __user *)&argp->freq[i],
					&node->sp->table[i].frequency,
					sizeof(uint32_t));
			if (ret)
				break;
		}

		break;
	default:
		break;
	}

	return ret;
}

#ifdef CONFIG_COMPAT
static long msm_core_compat_ioctl(struct file *file, unsigned int cmd,
		unsigned long arg)
{
	arg = (unsigned long)compat_ptr(arg);
	return msm_core_ioctl(file, cmd, arg);
}
#endif

static int msm_core_open(struct inode *inode, struct file *file)
{
	return 0;
}

static int msm_core_release(struct inode *inode, struct file *file)
{
	return 0;
}

static inline void init_sens_threshold(struct sensor_threshold *threshold,
		enum thermal_trip_type trip, long temp,
		void *data)
{
	threshold->trip = trip;
	threshold->temp = temp;
	threshold->data = data;
	threshold->notify = (void *)core_temp_notify;
}

static int msm_core_stats_init(struct device *dev)
{
	int cpu;
	int i;
	struct cpu_activity_info *cpu_node;
	struct cpu_pstate_pwr *pstate = NULL;

	for_each_possible_cpu(cpu) {
		cpu_node = &activity[cpu];
		cpu_stats[cpu].cpu = cpu;
		cpu_stats[cpu].temp = cpu_node->temp;
		cpu_stats[cpu].len = cpu_node->sp->num_of_freqs;
		pstate = devm_kzalloc(dev,
			sizeof(*pstate) * cpu_node->sp->num_of_freqs,
			GFP_KERNEL);
		if (!pstate)
			return -ENOMEM;

		for (i = 0; i < cpu_node->sp->num_of_freqs; i++)
			pstate[i].freq = cpu_node->sp->table[i].frequency;

		per_cpu(ptable, cpu) = pstate;
		cpu_stats[cpu].ptable = per_cpu(ptable, cpu);
		repopulate_stats(cpu);
	}
	return 0;
}

static int msm_core_task_init(struct device *dev)
{
	init_completion(&sampling_completion);
	sampling_task = kthread_run(do_sampling, NULL, "msm-core:sampling");
	if (IS_ERR(sampling_task)) {
		pr_err("Failed to create do_sampling err: %ld\n",
				PTR_ERR(sampling_task));
		return PTR_ERR(sampling_task);
	}
	return 0;
}

struct cpu_pwr_stats *get_cpu_pwr_stats(void)
{
	return cpu_stats;
}
EXPORT_SYMBOL(get_cpu_pwr_stats);

static int msm_get_power_values(int cpu, struct cpu_static_info *sp)
{
	int i = 0, j;
	int ret = 0;
	uint64_t power;

	/* Calculate dynamic power spent for every frequency using formula:
	 * Power = V * V * f
	 * where V = voltage for frequency
	 *       f = frequency
	 * */
	sp->power = allocate_2d_array_uint32_t(sp->num_of_freqs);
	if (IS_ERR_OR_NULL(sp->power))
		return PTR_ERR(sp->power);

	for (i = 0; i < TEMP_DATA_POINTS; i++) {
		for (j = 0; j < sp->num_of_freqs; j++) {
			power = sp->voltage[j] *
						sp->table[j].frequency;
			do_div(power, 1000);
			do_div(power, 1000);
			power *= sp->voltage[j];
			do_div(power, 1000);
			sp->power[i][j] = power;
		}
	}
	return ret;
}

static int msm_get_voltage_levels(struct device *dev, int cpu,
		struct cpu_static_info *sp)
{
	unsigned int *voltage;
	int i;
	int volt;
	struct opp *opp;
	struct platform_device *pdev =
		of_find_device_by_node(activity[cpu].apc_node);
	int a53_rail[4] = {0, 900000, 1000000, 1115000};

	voltage = devm_kzalloc(dev,
			sizeof(*voltage) * sp->num_of_freqs, GFP_KERNEL);

	if (!voltage)
		return -ENOMEM;

	rcu_read_lock();
	for (i = 0; i < sp->num_of_freqs; i++) {
		opp = dev_pm_opp_find_freq_exact(&pdev->dev,
				sp->table[i].frequency * 1000, true);
		volt = dev_pm_opp_get_voltage(opp);
		voltage[i] = a53_rail[volt] / 1000;
	}
	rcu_read_unlock();

	sp->voltage = voltage;
	return 0;
}

static int msm_core_dyn_pwr_init(struct platform_device *pdev,
				struct device_node *node,
				int cpu)
{
	int ret = 0;
	int i;
	struct platform_device *pdev_rail;

	activity[cpu].sp = kzalloc(sizeof(*(activity[cpu].sp)), GFP_KERNEL);
	if (!activity[cpu].sp)
		return -ENOMEM;

	pdev_rail = of_find_device_by_node(activity[cpu].apc_node);
	ret = dev_pm_opp_init_cpufreq_table(&pdev_rail->dev,
			&activity[cpu].sp->table);
	if (ret) {
		pr_err("Couldn't init freq table for cpu%d: %d\n", cpu, ret);
		return ret;
	}

	for (i = 0; activity[cpu].sp->table[i].frequency != CPUFREQ_TABLE_END;
			i++)
		activity[cpu].sp->num_of_freqs++;

	ret = msm_get_voltage_levels(&pdev->dev, cpu, activity[cpu].sp);
	if (ret)
		return ret;

	ret = msm_get_power_values(cpu, activity[cpu].sp);
	if (ret)
		return ret;

	return 0;
}

static int msm_core_tsens_init(struct device_node *node, int cpu)
{
	int ret = 0;
	char *key = NULL;
	struct device_node *phandle;
	const char *sensor_type = NULL;
	struct cpu_activity_info *cpu_node = &activity[cpu];

	if (!node)
		return -ENODEV;

	key = "qcom,sensor";
	phandle = of_parse_phandle(node, key, 0);
	if (!phandle) {
		pr_info("%s: No sensor mapping found for the core\n",
				__func__);
		/* Do not treat this as error as some targets might have
		 * temperature notification only in userspace.
		 * Use default temperature for the core. Userspace might
		 * update the temperature once it is up.
		 */
		cpu_node->sensor_id = -ENODEV;
		cpu_node->temp = DEFAULT_TEMP;
		return 0;
	}

	key = "qcom,sensor-name";
	ret = of_property_read_string(phandle, key,
				&sensor_type);
	if (ret) {
		pr_err("%s: Cannot read tsens id\n", __func__);
		return ret;
	}

	cpu_node->sensor_id = sensor_get_id((char *)sensor_type);
	if (cpu_node->sensor_id < 0)
		return cpu_node->sensor_id;

	ret = sensor_get_temp(cpu_node->sensor_id, &cpu_node->temp);
	if (ret)
		return ret;

	init_sens_threshold(&cpu_node->hi_threshold,
			THERMAL_TRIP_CONFIGURABLE_HI,
			cpu_node->temp + high_hyst_temp,
			(void *)cpu_node);
	init_sens_threshold(&cpu_node->low_threshold,
			THERMAL_TRIP_CONFIGURABLE_LOW,
			cpu_node->temp - low_hyst_temp,
			(void *)cpu_node);

	return ret;
}

static int msm_core_mpidr_init(struct device_node *node)
{
	int ret = 0;
	char *key = NULL;
	struct device_node *phandle;
	int mpidr;

	if (!node)
		return -ENODEV;

	key = "qcom,cpu-name";
	phandle = of_parse_phandle(node, key, 0);
	if (!phandle) {
		pr_err("%s: Cannot map cpu handle\n", __func__);
		return -ENODEV;
	}

	key = "reg";
	ret = of_property_read_u32(phandle, key,
				&mpidr);
	if (ret) {
		pr_err("%s: Cannot read mpidr\n", __func__);
		return ret;
	}
	return mpidr;
}

static int msm_core_params_init(struct platform_device *pdev)
{
	int ret = 0;
	unsigned long cpu = 0;
	struct device_node *node = NULL;
	struct device_node *child_node = NULL;
	int mpidr;
	char *key = NULL;

	node = of_find_node_by_name(pdev->dev.of_node,
				"qcom,core-mapping");
	if (!node) {
		pr_err("No per core params found\n");
		return -ENODEV;
	}

	for_each_child_of_node(node, child_node) {
		mpidr = msm_core_mpidr_init(child_node);
		if (mpidr < 0)
			return mpidr;

		for_each_possible_cpu(cpu)
			if (cpu_logical_map(cpu) == mpidr)
				break;

		if (cpu >= num_possible_cpus())
			continue;

		activity[cpu].mpidr = mpidr;

		key = "qcom,apc-rail";

		activity[cpu].apc_node = of_parse_phandle(child_node, key, 0);
		if (!activity[cpu].apc_node) {
			pr_err("%s: Couldn't find the opp apcrail\n",
					__func__);
			return -ENODEV;
		}

		ret = msm_core_tsens_init(child_node, cpu);
		if (ret)
			return ret;

		ret = msm_core_dyn_pwr_init(pdev, child_node, cpu);
		if (ret)
			return ret;

	}
	return 0;
}

static const struct file_operations msm_core_ops = {
	.owner = THIS_MODULE,
	.unlocked_ioctl = msm_core_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = msm_core_compat_ioctl,
#endif
	.open = msm_core_open,
	.release = msm_core_release,
};

static struct miscdevice msm_core_device = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "pta",
	.fops = &msm_core_ops
};

static void free_dyn_memory(void)
{
	int i, cpu;

	for_each_possible_cpu(cpu) {
		if (activity[cpu].sp) {
			for (i = 0; i < TEMP_DATA_POINTS; i++)
				kfree(activity[cpu].sp->power[i]);
		}
		kfree(activity[cpu].sp);
	}
}

static int msm_core_dev_probe(struct platform_device *pdev)
{
	int ret = 0;
	char *key = NULL;
	struct device_node *node;
	int cpu;

	if (!pdev)
		return -ENODEV;

	node = pdev->dev.of_node;
	if (!node)
		return -ENODEV;

	key = "qcom,low-hyst-temp";
	ret = of_property_read_u32(node, key, &low_hyst_temp);
	if (ret)
		low_hyst_temp = DEFAULT_LOW_HYST_TEMP;

	key = "qcom,high-hyst-temp";
	ret = of_property_read_u32(node, key, &high_hyst_temp);
	if (ret)
		high_hyst_temp = DEFAULT_HIGH_HYST_TEMP;

	key = "qcom,polling-interval";
	ret = of_property_read_u32(node, key, &poll_ms);
	if (ret)
		pr_info("msm-core initialized without polling period\n");

	ret = misc_register(&msm_core_device);
	if (ret) {
		pr_err("%s: Error registering device %d\n", __func__, ret);
		return ret;
	}

	ret = msm_core_params_init(pdev);
	if (ret)
		goto failed;

	ret = msm_core_stats_init(&pdev->dev);
	if (ret)
		goto failed;

	ret = msm_core_task_init(&pdev->dev);
	if (ret)
		goto failed;

	for_each_possible_cpu(cpu)
		set_threshold(&activity[cpu]);

	INIT_DEFERRABLE_WORK(&sampling_work, samplequeue_handle);
	schedule_delayed_work(&sampling_work, msecs_to_jiffies(0));
	return 0;
failed:
	free_dyn_memory();
	return ret;
}

static int msm_core_remove(struct platform_device *pdev)
{
	int cpu;

	for_each_possible_cpu(cpu) {
		if (activity[cpu].sensor_id < 0)
			continue;

		sensor_cancel_trip(activity[cpu].sensor_id,
				&activity[cpu].hi_threshold);
		sensor_cancel_trip(activity[cpu].sensor_id,
				&activity[cpu].low_threshold);
	}
	free_dyn_memory();
	misc_deregister(&msm_core_device);
	return 0;
}

static struct of_device_id msm_core_match_table[] = {
	{.compatible = "qcom,apss-core-ea"},
	{},
};

static struct platform_driver msm_core_driver = {
	.probe = msm_core_dev_probe,
	.driver = {
		.name = "msm_core",
		.owner = THIS_MODULE,
		.of_match_table = msm_core_match_table,
		},
	.remove = msm_core_remove,
};

static int __init msm_core_init(void)
{
	return platform_driver_register(&msm_core_driver);
}
late_initcall(msm_core_init);
+13 −0
Original line number Diff line number Diff line
/* Copyright (c) 2014, 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 <uapi/linux/msm-core-interface.h>
Loading