1125 lines
25 KiB
C
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);
|