M7350/kernel/drivers/power/qcom/msm-core.c
2024-09-09 08:57:42 +00:00

1125 lines
25 KiB
C

/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/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/suspend.h>
#include <linux/thermal.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/uio_driver.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_HOTPLUG_LIMIT 80
#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 NUM_OF_CORNERS 10
#define DEFAULT_SCALING_FACTOR 1
#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 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 DEFINE_MUTEX(policy_update_mutex);
static DEFINE_MUTEX(kthread_update_mutex);
static DEFINE_SPINLOCK(update_lock);
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 platform_device *msm_core_pdev;
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];
static uint32_t scaling_factor;
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);
static bool in_suspend;
static bool activate_power_table;
static int max_throttling_temp = 80; /* in C */
module_param_named(throttling_temp, max_throttling_temp, 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;
/*
* Before operating on the threshold structure which is used by
* thermal core ensure that the sensor is disabled to prevent
* incorrect operations on the sensor list maintained by thermal code.
*/
sensor_activate_trip(cpu_node->sensor_id,
&cpu_node->hi_threshold, false);
sensor_activate_trip(cpu_node->sensor_id,
&cpu_node->low_threshold, false);
cpu_node->hi_threshold.temp = (cpu_node->temp + high_hyst_temp) *
scaling_factor;
cpu_node->low_threshold.temp = (cpu_node->temp - low_hyst_temp) *
scaling_factor;
/*
* Set the threshold only if we are below the hotplug limit
* Adding more work at this high temperature range, seems to
* fail hotplug notifications.
*/
if (cpu_node->hi_threshold.temp < (CPU_HOTPLUG_LIMIT * scaling_factor))
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 / scaling_factor;
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 (!pt)
return;
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];
struct cpu_activity_info *cpu_node;
long temp;
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, &temp);
cpu_node->temp = temp / scaling_factor;
prev_temp[cpu] = cpu_node->temp;
/*
* Do not populate/update stats before policy and ptable have
* been updated.
*/
if (activate_power_table && cpu_stats[cpu].ptable
&& cpu_node->sp->table)
repopulate_stats(cpu);
}
spin_unlock(&update_lock);
}
EXPORT_SYMBOL(trigger_cpu_pwr_stats_calc);
void set_cpu_throttled(cpumask_t *mask, bool throttling)
{
int cpu;
if (!mask)
return;
spin_lock(&update_lock);
for_each_cpu(cpu, mask)
cpu_stats[cpu].throttling = throttling;
spin_unlock(&update_lock);
}
EXPORT_SYMBOL(set_cpu_throttled);
static void update_related_freq_table(struct cpufreq_policy *policy)
{
int cpu, num_of_freqs;
struct cpufreq_frequency_table *table;
table = cpufreq_frequency_get_table(policy->cpu);
if (!table) {
pr_err("Couldn't get freq table for cpu%d\n",
policy->cpu);
return;
}
for (num_of_freqs = 0; table[num_of_freqs].frequency !=
CPUFREQ_TABLE_END;)
num_of_freqs++;
/*
* Synchronous cores within cluster have the same
* policy. Since these cores do not have the cpufreq
* table initialized for all of them, copy the same
* table to all the related cpus.
*/
for_each_cpu(cpu, policy->related_cpus) {
activity[cpu].sp->table = table;
activity[cpu].sp->num_of_freqs = num_of_freqs;
}
}
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);
mutex_lock(&kthread_update_mutex);
if (in_suspend)
goto unlock;
trigger_cpu_pwr_stats_calc();
for_each_online_cpu(cpu) {
cpu_node = &activity[cpu];
if (prev_temp[cpu] != cpu_node->temp) {
prev_temp[cpu] = cpu_node->temp;
set_threshold(cpu_node);
trace_temp_threshold(cpu, cpu_node->temp,
cpu_node->hi_threshold.temp /
scaling_factor,
cpu_node->low_threshold.temp /
scaling_factor);
}
}
if (!poll_ms)
goto unlock;
schedule_delayed_work(&sampling_work,
msecs_to_jiffies(poll_ms));
unlock:
mutex_unlock(&kthread_update_mutex);
}
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);
}
BLOCKING_NOTIFIER_HEAD(msm_core_stats_notifier_list);
struct blocking_notifier_head *get_power_update_notifier(void)
{
return &msm_core_stats_notifier_list;
}
int register_cpu_pwr_stats_ready_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&msm_core_stats_notifier_list,
nb);
}
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 cpumask, cluster, mpidr;
get_user(cpumask, &argp->cpumask);
get_user(cluster, &argp->cluster);
mpidr = cluster << 8;
pr_debug("%s: cpumask %d, cluster: %d\n", __func__, cpumask,
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)
*/
spin_lock(&update_lock);
get_user(cpumask, &argp->cpumask);
for (i = 0; i < MAX_CORES_PER_CLUSTER; i++, cpumask >>= 1) {
if (!(cpumask & 0x01))
continue;
mpidr = (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);
}
cpu_stats[cpu].ptable = per_cpu(ptable, cpu);
repopulate_stats(cpu);
blocking_notifier_call_chain(
&msm_core_stats_notifier_list, cpu, NULL);
}
}
spin_unlock(&update_lock);
activate_power_table = true;
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, cluster, cpumask;
if (!argp)
return -EINVAL;
get_user(cluster, &argp->cluster);
mpidr = (argp->cluster << (MAX_CORES_PER_CLUSTER *
MAX_NUM_OF_CLUSTERS));
cpumask = argp->cpumask;
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; cpumask > 0; i++, cpumask >>= 1) {
for_each_possible_cpu(cpu) {
if (cpu_logical_map(cpu) == (mpidr | i))
break;
}
}
if (cpu >= num_possible_cpus())
break;
mutex_lock(&policy_update_mutex);
node = &activity[cpu];
if (!node->sp->table) {
ret = -EINVAL;
goto unlock;
}
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;
}
unlock:
mutex_unlock(&policy_update_mutex);
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;
cpu_node = &activity[cpu];
cpu_stats[cpu].cpu = cpu;
cpu_stats[cpu].temp = cpu_node->temp;
cpu_stats[cpu].throttling = false;
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;
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 corner;
struct dev_pm_opp *opp;
struct device *cpu_dev = get_cpu_device(cpu);
/*
* Convert cpr corner voltage to average voltage of both
* a53 and a57 votlage value
*/
int average_voltage[NUM_OF_CORNERS] = {0, 746, 841, 843, 940, 953, 976,
1024, 1090, 1100};
if (!cpu_dev)
return -ENODEV;
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(cpu_dev,
sp->table[i].frequency * 1000, true);
corner = dev_pm_opp_get_voltage(opp);
if (corner > 400000)
voltage[i] = corner / 1000;
else if (corner > 0 && corner < ARRAY_SIZE(average_voltage))
voltage[i] = average_voltage[corner];
else
voltage[i]
= average_voltage[ARRAY_SIZE(average_voltage) - 1];
}
rcu_read_unlock();
sp->voltage = voltage;
return 0;
}
static int msm_core_dyn_pwr_init(struct platform_device *pdev,
int cpu)
{
int ret = 0;
if (!activity[cpu].sp->table)
return 0;
ret = msm_get_voltage_levels(&pdev->dev, cpu, activity[cpu].sp);
if (ret)
return ret;
ret = msm_get_power_values(cpu, activity[cpu].sp);
return ret;
}
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];
long temp;
if (!node)
return -ENODEV;
key = "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;
key = "qcom,scaling-factor";
ret = of_property_read_u32(phandle, key,
&scaling_factor);
if (ret) {
pr_info("%s: Cannot read tsens scaling factor\n", __func__);
scaling_factor = DEFAULT_SCALING_FACTOR;
}
ret = sensor_get_temp(cpu_node->sensor_id, &temp);
if (ret)
return ret;
cpu_node->temp = temp / scaling_factor;
init_sens_threshold(&cpu_node->hi_threshold,
THERMAL_TRIP_CONFIGURABLE_HI,
(cpu_node->temp + high_hyst_temp) * scaling_factor,
(void *)cpu_node);
init_sens_threshold(&cpu_node->low_threshold,
THERMAL_TRIP_CONFIGURABLE_LOW,
(cpu_node->temp - low_hyst_temp) * scaling_factor,
(void *)cpu_node);
return ret;
}
static int msm_core_mpidr_init(struct device_node *phandle)
{
int ret = 0;
char *key = NULL;
int mpidr;
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_cpu_policy_handler(struct notifier_block *nb,
unsigned long val, void *data)
{
struct cpufreq_policy *policy = data;
struct cpu_activity_info *cpu_info = &activity[policy->cpu];
int cpu;
int ret;
if (cpu_info->sp->table)
return NOTIFY_OK;
switch (val) {
case CPUFREQ_CREATE_POLICY:
mutex_lock(&policy_update_mutex);
update_related_freq_table(policy);
for_each_cpu(cpu, policy->related_cpus) {
ret = msm_core_dyn_pwr_init(msm_core_pdev, cpu);
if (ret)
pr_debug("voltage-pwr table update failed\n");
ret = msm_core_stats_init(&msm_core_pdev->dev, cpu);
if (ret)
pr_debug("Stats table update failed\n");
}
mutex_unlock(&policy_update_mutex);
break;
default:
break;
}
return NOTIFY_OK;
}
struct notifier_block cpu_policy = {
.notifier_call = msm_core_cpu_policy_handler
};
static int system_suspend_handler(struct notifier_block *nb,
unsigned long val, void *data)
{
int cpu;
mutex_lock(&kthread_update_mutex);
switch (val) {
case PM_POST_HIBERNATION:
case PM_POST_SUSPEND:
case PM_POST_RESTORE:
/*
* Set completion event to read temperature and repopulate
* stats
*/
in_suspend = 0;
complete(&sampling_completion);
break;
case PM_HIBERNATION_PREPARE:
case PM_SUSPEND_PREPARE:
/*
* cancel delayed work to be able to restart immediately
* after system resume
*/
in_suspend = 1;
cancel_delayed_work(&sampling_work);
/*
* cancel TSENS interrupts as we do not want to wake up from
* suspend to take care of repopulate stats while the system is
* in suspend
*/
for_each_possible_cpu(cpu) {
if (activity[cpu].sensor_id < 0)
continue;
sensor_activate_trip(activity[cpu].sensor_id,
&activity[cpu].hi_threshold, false);
sensor_activate_trip(activity[cpu].sensor_id,
&activity[cpu].low_threshold, false);
}
break;
default:
break;
}
mutex_unlock(&kthread_update_mutex);
return NOTIFY_OK;
}
static int msm_core_freq_init(void)
{
int cpu;
struct cpufreq_policy *policy;
for_each_possible_cpu(cpu) {
activity[cpu].sp = kzalloc(sizeof(*(activity[cpu].sp)),
GFP_KERNEL);
if (!activity[cpu].sp)
return -ENOMEM;
}
for_each_online_cpu(cpu) {
if (activity[cpu].sp->table)
continue;
policy = cpufreq_cpu_get(cpu);
if (!policy)
continue;
update_related_freq_table(policy);
cpufreq_cpu_put(policy);
}
return 0;
}
static int msm_core_params_init(struct platform_device *pdev)
{
int ret = 0;
unsigned long cpu = 0;
struct device_node *child_node = NULL;
struct device_node *ea_node = NULL;
char *key = NULL;
int mpidr;
for_each_possible_cpu(cpu) {
child_node = of_get_cpu_node(cpu, NULL);
if (!child_node)
continue;
mpidr = msm_core_mpidr_init(child_node);
if (mpidr < 0)
return mpidr;
if (cpu >= num_possible_cpus())
continue;
activity[cpu].mpidr = mpidr;
key = "qcom,ea";
ea_node = of_parse_phandle(child_node, key, 0);
if (!ea_node) {
pr_err("%s Couldn't find the ea_node for cpu%lu\n",
__func__, cpu);
return -ENODEV;
}
ret = msm_core_tsens_init(ea_node, cpu);
if (ret)
return ret;
if (!activity[cpu].sp->table)
continue;
ret = msm_core_dyn_pwr_init(msm_core_pdev, cpu);
if (ret)
pr_debug("voltage-pwr table update failed\n");
ret = msm_core_stats_init(&msm_core_pdev->dev, cpu);
if (ret)
pr_debug("Stats table update failed\n");
}
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++) {
if (!activity[cpu].sp->power)
break;
kfree(activity[cpu].sp->power[i]);
}
}
kfree(activity[cpu].sp);
}
}
static int uio_init(struct platform_device *pdev)
{
int ret = 0;
struct uio_info *info = NULL;
struct resource *clnt_res = NULL;
u32 ea_mem_size = 0;
phys_addr_t ea_mem_pyhsical = 0;
clnt_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!clnt_res) {
pr_err("resource not found\n");
return -ENODEV;
}
info = devm_kzalloc(&pdev->dev, sizeof(struct uio_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
ea_mem_size = resource_size(clnt_res);
ea_mem_pyhsical = clnt_res->start;
if (ea_mem_size == 0) {
pr_err("msm-core: memory size is zero");
return -EINVAL;
}
/* Setup device */
info->name = clnt_res->name;
info->version = "1.0";
info->mem[0].addr = ea_mem_pyhsical;
info->mem[0].size = ea_mem_size;
info->mem[0].memtype = UIO_MEM_PHYS;
ret = uio_register_device(&pdev->dev, info);
if (ret) {
pr_err("uio register failed ret=%d", ret);
return ret;
}
dev_set_drvdata(&pdev->dev, info);
return 0;
}
static int msm_core_dev_probe(struct platform_device *pdev)
{
int ret = 0;
char *key = NULL;
struct device_node *node;
int cpu;
struct uio_info *info;
if (!pdev)
return -ENODEV;
msm_core_pdev = pdev;
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");
key = "qcom,throttling-temp";
ret = of_property_read_u32(node, key, &max_throttling_temp);
ret = uio_init(pdev);
if (ret)
return ret;
ret = msm_core_freq_init();
if (ret)
goto failed;
ret = misc_register(&msm_core_device);
if (ret) {
pr_err("%s: Error registering device %d\n", __func__, ret);
goto failed;
}
ret = msm_core_params_init(pdev);
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));
cpufreq_register_notifier(&cpu_policy, CPUFREQ_POLICY_NOTIFIER);
pm_notifier(system_suspend_handler, 0);
return 0;
failed:
info = dev_get_drvdata(&pdev->dev);
uio_unregister_device(info);
free_dyn_memory();
return ret;
}
static int msm_core_remove(struct platform_device *pdev)
{
int cpu;
struct uio_info *info = dev_get_drvdata(&pdev->dev);
uio_unregister_device(info);
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);