817 lines
26 KiB
C
Executable file
817 lines
26 KiB
C
Executable file
/*
|
|
* exynos_amb_control.c - Samsung AMB control (Ambient Thermal Control module)
|
|
*
|
|
* Copyright (C) 2021 Samsung Electronics
|
|
* Hanjun Shin <hanjun.shin@samsung.com>
|
|
* Youngjin Lee <youngjin0.lee@samsung.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/suspend.h>
|
|
#include <linux/threads.h>
|
|
#include <linux/thermal.h>
|
|
#include <linux/slab.h>
|
|
#include <soc/samsung/exynos-cpuhp.h>
|
|
#include <uapi/linux/sched/types.h>
|
|
|
|
#include <soc/samsung/exynos_amb_control.h>
|
|
#include <soc/samsung/exynos-mcinfo.h>
|
|
#include <soc/samsung/exynos-sci.h>
|
|
|
|
#include "exynos_tmu.h"
|
|
|
|
#define AMB_TZ_NUM (5)
|
|
|
|
#define DEFAULT_SAMPLING_RATE 1000
|
|
#define HIGH_SAMPLING_RATE 100
|
|
#define INCREASE_SAMPLING_TEMP 55000
|
|
#define DECREASE_SAMPLING_TEMP 50000
|
|
#define HOTPLUG_IN_THRESHOLD 60000
|
|
#define HOTPLUG_OUT_THRESHOLD 65000
|
|
|
|
#define TRIGGER_COND_HIGH 0
|
|
#define TRIGGER_COND_LOW 1
|
|
#define TRIGGER_COND_NOTUSED 2
|
|
|
|
static const char * trigger_cond_desc[3] = { "high", "low", "not_used" };
|
|
|
|
static struct exynos_amb_control_data *amb_control;
|
|
|
|
static int exynos_amb_control_set_polling(struct exynos_amb_control_data *data,
|
|
unsigned int delay)
|
|
{
|
|
kthread_mod_delayed_work(&data->amb_worker, &data->amb_dwork,
|
|
msecs_to_jiffies(delay));
|
|
return 0;
|
|
}
|
|
|
|
/* sysfs nodes for amb control */
|
|
#define sysfs_printf(...) count += snprintf(buf + count, PAGE_SIZE, __VA_ARGS__)
|
|
static ssize_t
|
|
exynos_amb_control_info_show(struct device *dev, struct device_attribute *devattr,
|
|
char *buf)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct exynos_amb_control_data *data = platform_get_drvdata(pdev);
|
|
unsigned int count = 0, i, j;
|
|
|
|
mutex_lock(&data->lock);
|
|
|
|
sysfs_printf("Exynos AMB Control Informations\n\n");
|
|
sysfs_printf("AMB Thermal Zone Info\n");
|
|
sysfs_printf("AMB thermal zone name : %s\n", data->amb_tzd->type);
|
|
sysfs_printf("Default sampling rate : %u\n", data->default_sampling_rate);
|
|
sysfs_printf("High sampling rate : %u\n", data->high_sampling_rate);
|
|
sysfs_printf("Increase sampling temp : %u\n", data->increase_sampling_temp);
|
|
sysfs_printf("Decrease sampling temp : %u\n", data->decrease_sampling_temp);
|
|
sysfs_printf("==============================================\n");
|
|
|
|
if (data->use_mif_throttle) {
|
|
sysfs_printf("MIF throttle temp : %u\n", data->mif_down_threshold);
|
|
sysfs_printf("MIF release temp : %u\n", data->mif_up_threshold);
|
|
sysfs_printf("MIF throttle freq : %u\n", data->mif_throttle_freq);
|
|
sysfs_printf("==============================================\n");
|
|
}
|
|
|
|
sysfs_printf("Thermal Zone Info\n");
|
|
|
|
for (i = 0; i < data->num_amb_tz_configs; i++) {
|
|
struct ambient_thermal_zone_config *tz_config = &data->amb_tz_config[i];
|
|
|
|
if (!cpumask_empty(&tz_config->cpu_domain)) {
|
|
sysfs_printf("CPU hotplug domains : 0x%x\n", *(unsigned int *)cpumask_bits(&tz_config->cpu_domain));
|
|
sysfs_printf("CPU hotplug out temp : %u\n", tz_config->hotplug_out_threshold);
|
|
sysfs_printf("CPU hotplug in temp : %u\n", tz_config->hotplug_in_threshold);
|
|
} else {
|
|
sysfs_printf("Thermal zone %s is not using CPU hotplug\n", tz_config->tzd->type);
|
|
}
|
|
|
|
for (j = 0; j < tz_config->num_throttle_configs; j++) {
|
|
struct exynos_amb_throttle_config *throttle_config = &tz_config->throttle_config[j];
|
|
|
|
sysfs_printf("\nThrottle config #%u\n", j);
|
|
sysfs_printf("Trip point : %u\n", throttle_config->trip_point);
|
|
sysfs_printf("Trigger condition : %s\n", trigger_cond_desc[throttle_config->trigger_cond]);
|
|
sysfs_printf("Trigger temp : %u\n", throttle_config->trigger_temp);
|
|
sysfs_printf("Release temp : %u\n", throttle_config->release_temp);
|
|
sysfs_printf("Amb trip temp : %u\n", throttle_config->amb_throttle_temp);
|
|
}
|
|
sysfs_printf("==============================================\n");
|
|
}
|
|
|
|
mutex_unlock(&data->lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
exynos_amb_control_set_config_show(struct device *dev, struct device_attribute *devattr,
|
|
char *buf)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct exynos_amb_control_data *data = platform_get_drvdata(pdev);
|
|
unsigned int count = 0, i;
|
|
|
|
sysfs_printf("Usage for set_config node\n");
|
|
|
|
sysfs_printf("[0] AMB thermal zone config (sampling rate/temp)\n");
|
|
sysfs_printf("\t# echo 0 [type] [value] > amb_control_set_config\n");
|
|
sysfs_printf("\ttype : 0/1/2/3=default_sampling_rate/high_sampling_rate/increase_sampling_temp/decrease_sampling_temp\n");
|
|
|
|
sysfs_printf("[1] CPU Hotplug config (hotplug temp/domain)\n");
|
|
sysfs_printf("\t# echo 1 [zone] [type] [value] > amb_control_set_config\n");
|
|
sysfs_printf("\tzone : ");
|
|
for (i = 0; i < data->num_amb_tz_configs - 1; i++)
|
|
sysfs_printf("%u/", i);
|
|
sysfs_printf("%u=", i);
|
|
sysfs_printf("\ttype : 0/1/2/3=hotplug_in_threshold/hotplug_out_threshold/hotplug_enable/cpu_domain(hex)\n");
|
|
|
|
sysfs_printf("[2] Throttle config (throttle temp, trip porint)\n");
|
|
sysfs_printf("\t# echo 2 [zone] [trip_point] [type] [value] > amb_control_set_config\n");
|
|
sysfs_printf("\tzone : ");
|
|
for (i = 0; i < data->num_amb_tz_configs - 1; i++)
|
|
sysfs_printf("%u/", i);
|
|
sysfs_printf("%u=", i);
|
|
for (i = 0; i < data->num_amb_tz_configs - 1; i++)
|
|
sysfs_printf("%s/", data->amb_tz_config[i].tzd->type);
|
|
sysfs_printf("%s\n", data->amb_tz_config[i].tzd->type);
|
|
sysfs_printf("\ttype : 0/1/2/3=trigger_condition/trigger_temp/release_temp/amb_throttle_temp\n");
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
exynos_amb_control_set_config_store(struct device *dev, struct device_attribute *devattr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct exynos_amb_control_data *data = platform_get_drvdata(pdev);
|
|
struct ambient_thermal_zone_config *tz_config = NULL;
|
|
struct exynos_amb_throttle_config *throttle_config = NULL;
|
|
unsigned int i, id, zone, type, value, trip_point;
|
|
char string[20];
|
|
|
|
mutex_lock(&data->lock);
|
|
|
|
if (sscanf(buf, "%d ", &id) != 1)
|
|
goto out;
|
|
|
|
if (id == 0) {
|
|
if (sscanf(buf, "%u %u %u", &id, &type, &value) != 3)
|
|
goto out;
|
|
|
|
switch (type) {
|
|
case 0:
|
|
data->default_sampling_rate = value;
|
|
break;
|
|
case 1:
|
|
data->high_sampling_rate = value;
|
|
break;
|
|
case 2:
|
|
data->increase_sampling_temp = value;
|
|
break;
|
|
case 3:
|
|
data->decrease_sampling_temp = value;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if (id == 1) {
|
|
if (sscanf(buf, "%u %u %u ", &id, &zone, &type) != 3)
|
|
goto out;
|
|
|
|
if (type == 3) {
|
|
if (sscanf(buf, "%u %u %u 0x%x", &id, &zone, &type, &value) != 4)
|
|
goto out;
|
|
} else {
|
|
if (sscanf(buf, "%u %u %u %u", &id, &zone, &type, &value) != 4)
|
|
goto out;
|
|
}
|
|
|
|
if (zone >= data->num_amb_tz_configs)
|
|
goto out;
|
|
|
|
tz_config = &data->amb_tz_config[zone];
|
|
|
|
switch (type) {
|
|
case 0:
|
|
if (value < 125000)
|
|
tz_config->hotplug_in_threshold = value;
|
|
break;
|
|
case 1:
|
|
if (value < 125000)
|
|
tz_config->hotplug_out_threshold = value;
|
|
break;
|
|
case 2:
|
|
tz_config->hotplug_enable = !!value;
|
|
break;
|
|
case 3:
|
|
snprintf(string, sizeof(string), "%x", value);
|
|
cpumask_parse(string, &tz_config->cpu_domain);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if (id == 2) {
|
|
if (sscanf(buf, "%u %u %u %u %u", &id, &zone, &trip_point, &type, &value) != 5)
|
|
goto out;
|
|
|
|
if (zone >= data->num_amb_tz_configs)
|
|
goto out;
|
|
|
|
tz_config = &data->amb_tz_config[zone];
|
|
|
|
for (i = 0; i < tz_config->num_throttle_configs; i++) {
|
|
if (tz_config->throttle_config[i].trip_point == trip_point) {
|
|
throttle_config = &tz_config->throttle_config[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (throttle_config) {
|
|
switch (type) {
|
|
case 0:
|
|
if (value < 3)
|
|
throttle_config->trigger_cond = value;
|
|
break;
|
|
case 1:
|
|
if (value < 125000)
|
|
throttle_config->trigger_temp = value;
|
|
break;
|
|
case 2:
|
|
if (value < 125000)
|
|
throttle_config->release_temp = value;
|
|
break;
|
|
case 3:
|
|
if (value < 125000)
|
|
throttle_config->amb_throttle_temp = value;
|
|
break;
|
|
default:
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
mutex_unlock(&data->lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
exynos_amb_control_mode_show(struct device *dev, struct device_attribute *devattr,
|
|
char *buf)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct exynos_amb_control_data *data = platform_get_drvdata(pdev);
|
|
unsigned int count = 0;
|
|
|
|
mutex_lock(&data->lock);
|
|
|
|
sysfs_printf("%s\n", data->amb_disabled ? "disabled" : "enabled");
|
|
|
|
mutex_unlock(&data->lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
exynos_amb_control_mode_store(struct device *dev, struct device_attribute *devattr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(dev);
|
|
struct exynos_amb_control_data *data = platform_get_drvdata(pdev);
|
|
int new_mode, prev_mode;
|
|
|
|
mutex_lock(&data->lock);
|
|
|
|
sscanf(buf, "%d", &new_mode);
|
|
|
|
prev_mode = data->amb_disabled;
|
|
data->amb_disabled = !!new_mode;
|
|
|
|
mutex_unlock(&data->lock);
|
|
|
|
if (data->amb_disabled && !prev_mode) {
|
|
kthread_cancel_delayed_work_sync(&data->amb_dwork);
|
|
pr_info("%s: amb control is disabled\n", __func__);
|
|
} else if (!data->amb_disabled && prev_mode) {
|
|
exynos_amb_control_set_polling(data, data->current_sampling_rate);
|
|
pr_info("%s: amb control is enabled\n", __func__);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(amb_control_info, S_IRUGO, exynos_amb_control_info_show, NULL);
|
|
static DEVICE_ATTR(amb_control_set_config, S_IWUSR | S_IRUGO,
|
|
exynos_amb_control_set_config_show, exynos_amb_control_set_config_store);
|
|
static DEVICE_ATTR(amb_control_disable, S_IWUSR | S_IRUGO,
|
|
exynos_amb_control_mode_show, exynos_amb_control_mode_store);
|
|
|
|
static struct attribute *exynos_amb_control_attrs[] = {
|
|
&dev_attr_amb_control_info.attr,
|
|
&dev_attr_amb_control_set_config.attr,
|
|
&dev_attr_amb_control_disable.attr,
|
|
NULL,
|
|
};
|
|
|
|
static const struct attribute_group exynos_amb_control_attr_group = {
|
|
.attrs = exynos_amb_control_attrs,
|
|
};
|
|
|
|
/* what does amb do ?
|
|
*
|
|
* set hotplug in/out theshold when amb temp is reaches on given value
|
|
* change control temp of cpu
|
|
* change throttle temp of ISP
|
|
*/
|
|
static int exynos_amb_controller(struct exynos_amb_control_data *data)
|
|
{
|
|
unsigned int i, j;
|
|
int amb_temp = 0;
|
|
struct cpumask mask;
|
|
|
|
mutex_lock(&data->lock);
|
|
|
|
if (data->in_suspend || data->amb_disabled) {
|
|
pr_info("%s: amb control is %s\n", __func__, data->in_suspend ? "suspended" : "disabled");
|
|
goto out;
|
|
}
|
|
|
|
/* Get temperature */
|
|
thermal_zone_get_temp(data->amb_tzd, &amb_temp);
|
|
|
|
data->amb_temp = amb_temp;
|
|
|
|
if (amb_temp == 0) {
|
|
data->current_sampling_rate = data->high_sampling_rate;
|
|
goto polling_out;
|
|
}
|
|
|
|
exynos_mcinfo_set_ambient_status(amb_temp > data->mcinfo_threshold);
|
|
|
|
cpumask_copy(&mask, cpu_possible_mask);
|
|
|
|
/* Check MIF throttle condition */
|
|
if (data->use_mif_throttle) {
|
|
if (amb_temp > data->mif_down_threshold)
|
|
exynos_pm_qos_update_request(&data->amb_mif_qos, data->mif_throttle_freq);
|
|
else if (amb_temp <= data->mif_up_threshold)
|
|
exynos_pm_qos_update_request(&data->amb_mif_qos, PM_QOS_BUS_THROUGHPUT_MAX_DEFAULT_VALUE);
|
|
}
|
|
|
|
/* Check LLC throttle condition */
|
|
if (data->use_llc_throttle) {
|
|
if (amb_temp > data->llc_off_threshold)
|
|
llc_disable_force(true);
|
|
else if (amb_temp <= data->llc_on_threshold)
|
|
llc_disable_force(false);
|
|
}
|
|
|
|
/* Traverse all amb tz config */
|
|
for (i = 0; i < data->num_amb_tz_configs; i++) {
|
|
struct ambient_thermal_zone_config *tz_config = &data->amb_tz_config[i];
|
|
|
|
/* Check hotplug condition */
|
|
if (!cpumask_empty(&tz_config->cpu_domain)) {
|
|
if (tz_config->is_cpu_hotplugged_out)
|
|
cpumask_andnot(&mask, &mask, &tz_config->cpu_domain);
|
|
|
|
if (!tz_config->is_cpu_hotplugged_out && amb_temp > tz_config->hotplug_out_threshold) {
|
|
tz_config->is_cpu_hotplugged_out = true;
|
|
cpumask_andnot(&mask, &mask, &tz_config->cpu_domain);
|
|
} else if (tz_config->is_cpu_hotplugged_out && amb_temp <= tz_config->hotplug_in_threshold) {
|
|
tz_config->is_cpu_hotplugged_out = false;
|
|
cpumask_or(&mask, &mask, &tz_config->cpu_domain);
|
|
}
|
|
}
|
|
|
|
/* Change throttle temp */
|
|
for (j = 0; j < tz_config->num_throttle_configs; j++) {
|
|
struct exynos_amb_throttle_config *throttle_config =
|
|
&tz_config->throttle_config[j];
|
|
struct exynos_tmu_data *tmu_data = exynos_tmu_get_data_from_tz(tz_config->tzd);
|
|
|
|
if (throttle_config->trigger_cond) {
|
|
// set amb throttle temp when amb temp is higher than trigger
|
|
if (amb_temp > throttle_config->trigger_temp && !throttle_config->status) {
|
|
if (!tz_config->amb_mode_cnt)
|
|
exynos_tmu_set_boost_mode(tmu_data, AMBIENT_MODE, true);
|
|
tz_config->amb_mode_cnt++;
|
|
|
|
tz_config->tzd->ops->set_trip_temp(
|
|
tz_config->tzd, throttle_config->trip_point,
|
|
throttle_config->amb_throttle_temp);
|
|
throttle_config->status = true;
|
|
} else if (amb_temp <= throttle_config->release_temp && throttle_config->status) {
|
|
tz_config->amb_mode_cnt--;
|
|
if (!tz_config->amb_mode_cnt)
|
|
exynos_tmu_set_boost_mode(tmu_data, THROTTLE_MODE, true);
|
|
throttle_config->status = false;
|
|
}
|
|
} else {
|
|
// set amb throttle temp when amb temp is lower than trigger
|
|
if (amb_temp <= throttle_config->trigger_temp && !throttle_config->status) {
|
|
if (!tz_config->amb_mode_cnt)
|
|
exynos_tmu_set_boost_mode(tmu_data, AMBIENT_MODE, true);
|
|
tz_config->amb_mode_cnt++;
|
|
|
|
tz_config->tzd->ops->set_trip_temp(
|
|
tz_config->tzd, throttle_config->trip_point,
|
|
throttle_config->amb_throttle_temp);
|
|
throttle_config->status = true;
|
|
} else if (amb_temp > throttle_config->release_temp && throttle_config->status) {
|
|
tz_config->amb_mode_cnt--;
|
|
if (!tz_config->amb_mode_cnt)
|
|
exynos_tmu_set_boost_mode(tmu_data, THROTTLE_MODE, true);
|
|
throttle_config->status = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!cpumask_equal(&data->cpuhp_req_mask, &mask)) {
|
|
exynos_cpuhp_update_request(&data->cpuhp_req, &mask);
|
|
cpumask_copy(&data->cpuhp_req_mask, &mask);
|
|
}
|
|
|
|
if (amb_temp > data->increase_sampling_temp)
|
|
data->current_sampling_rate = data->high_sampling_rate;
|
|
else if (amb_temp <= data->decrease_sampling_temp)
|
|
data->current_sampling_rate = data->default_sampling_rate;
|
|
polling_out:
|
|
exynos_amb_control_set_polling(data, data->current_sampling_rate);
|
|
out:
|
|
mutex_unlock(&data->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void exynos_amb_control_work_func(struct kthread_work *work)
|
|
{
|
|
struct exynos_amb_control_data *data =
|
|
container_of(work, struct exynos_amb_control_data, amb_dwork.work);
|
|
|
|
exynos_amb_controller(data);
|
|
};
|
|
|
|
static int exynos_amb_control_work_init(struct platform_device *pdev)
|
|
{
|
|
struct cpumask mask;
|
|
struct exynos_amb_control_data *data = platform_get_drvdata(pdev);
|
|
struct sched_param param = { .sched_priority = MAX_RT_PRIO / 4 - 1 };
|
|
struct task_struct *thread;
|
|
int ret = 0;
|
|
|
|
kthread_init_worker(&data->amb_worker);
|
|
thread = kthread_create(kthread_worker_fn, &data->amb_worker,
|
|
"thermal_amb");
|
|
if (IS_ERR(thread)) {
|
|
dev_err(&pdev->dev, "failed to create amb worker thread: %ld\n",
|
|
PTR_ERR(thread));
|
|
return PTR_ERR(thread);
|
|
}
|
|
|
|
cpulist_parse("0-3", &mask);
|
|
cpumask_and(&mask, cpu_possible_mask, &mask);
|
|
set_cpus_allowed_ptr(thread, &mask);
|
|
|
|
ret = sched_setscheduler_nocheck(thread, SCHED_FIFO, ¶m);
|
|
if (ret) {
|
|
kthread_stop(thread);
|
|
dev_warn(&pdev->dev, "failed to set amb worker thread as SCHED_FIFO\n");
|
|
return ret;
|
|
}
|
|
|
|
kthread_init_delayed_work(&data->amb_dwork, exynos_amb_control_work_func);
|
|
exynos_amb_control_set_polling(data, 0);
|
|
|
|
wake_up_process(thread);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int exynos_amb_control_parse_dt(struct platform_device *pdev)
|
|
{
|
|
const char *buf;
|
|
struct device_node *np, *child;
|
|
struct exynos_amb_control_data *data = platform_get_drvdata(pdev);
|
|
int i = 0;
|
|
|
|
np = pdev->dev.of_node;
|
|
|
|
/* Get common amb_control configs */
|
|
if (of_property_read_string(np, "amb_tz_name", &buf)) {
|
|
dev_err(&pdev->dev, "failed to get amb_tz_name\n");
|
|
return -ENODEV;
|
|
} else {
|
|
data->amb_tzd = thermal_zone_get_zone_by_name(buf);
|
|
if (!data->amb_tzd) {
|
|
dev_err(&pdev->dev, "failed to get amb_tzd\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
dev_info(&pdev->dev, "Amb tz name %s\n", data->amb_tzd->type);
|
|
}
|
|
|
|
if (of_property_read_u32(np, "default_sampling_rate", &data->default_sampling_rate))
|
|
data->default_sampling_rate = DEFAULT_SAMPLING_RATE;
|
|
|
|
dev_info(&pdev->dev, "Default sampling rate (%u)\n", data->default_sampling_rate);
|
|
|
|
if (of_property_read_u32(np, "high_sampling_rate", &data->high_sampling_rate))
|
|
data->high_sampling_rate = HIGH_SAMPLING_RATE;
|
|
dev_info(&pdev->dev, "High sampling rate (%u)\n", data->high_sampling_rate);
|
|
|
|
if (of_property_read_u32(np, "increase_sampling_temp", &data->increase_sampling_temp))
|
|
data->increase_sampling_temp = INCREASE_SAMPLING_TEMP;
|
|
dev_info(&pdev->dev, "Increase sampling temp (%u)\n", data->increase_sampling_temp);
|
|
|
|
if (of_property_read_u32(np, "decrease_sampling_temp", &data->decrease_sampling_temp))
|
|
data->decrease_sampling_temp = DECREASE_SAMPLING_TEMP;
|
|
dev_info(&pdev->dev, "decrease sampling temp (%u)\n", data->decrease_sampling_temp);
|
|
|
|
if (of_property_read_u32(np, "mcinfo_threshold", &data->mcinfo_threshold))
|
|
data->mcinfo_threshold = MCINFO_THRESHOLD_TEMP;
|
|
dev_info(&pdev->dev, "decrease sampling temp (%u)\n", data->mcinfo_threshold);
|
|
|
|
data->current_sampling_rate = data->default_sampling_rate;
|
|
dev_info(&pdev->dev, "Current sampling rate (%u)\n", data->current_sampling_rate);
|
|
|
|
data->use_mif_throttle = of_property_read_bool(np, "use_mif_throttle");
|
|
dev_info(&pdev->dev, "use mif throttle (%s)\n", data->use_mif_throttle ? "true" : "false");
|
|
if (data->use_mif_throttle) {
|
|
exynos_pm_qos_add_request(&data->amb_mif_qos,
|
|
PM_QOS_BUS_THROUGHPUT_MAX, PM_QOS_BUS_THROUGHPUT_MAX_DEFAULT_VALUE);
|
|
|
|
if (of_property_read_u32(np, "mif_down_threshold", &data->mif_down_threshold))
|
|
data->mif_down_threshold = MIF_DOWN_THRESHOLD_TEMP;
|
|
dev_info(&pdev->dev, "mif down threshold (%u)\n", data->mif_down_threshold);
|
|
|
|
if (of_property_read_u32(np, "mif_up_threshold", &data->mif_up_threshold))
|
|
data->mif_up_threshold = MIF_UP_THRESHOLD_TEMP;
|
|
dev_info(&pdev->dev, "mif up threshold (%u)\n", data->mif_up_threshold);
|
|
|
|
if (of_property_read_u32(np, "mif_throttle_freq", &data->mif_throttle_freq))
|
|
data->mif_throttle_freq = MIF_THROTTLE_FREQ;
|
|
|
|
dev_info(&pdev->dev, "mif throttle freq (%u)\n", data->mif_throttle_freq);
|
|
}
|
|
|
|
data->use_llc_throttle = of_property_read_bool(np, "use_llc_throttle");
|
|
dev_info(&pdev->dev, "use llc throttle (%s)\n", data->use_llc_throttle ? "true" : "false");
|
|
if (data->use_llc_throttle) {
|
|
if (of_property_read_u32(np, "llc_off_threshold", &data->llc_off_threshold))
|
|
data->llc_off_threshold = LLC_OFF_THRESHOLD_TEMP;
|
|
dev_info(&pdev->dev, "llc off threshold (%u)\n", data->llc_off_threshold);
|
|
|
|
if (of_property_read_u32(np, "llc_on_threshold", &data->llc_on_threshold))
|
|
data->llc_on_threshold = LLC_ON_THRESHOLD_TEMP;
|
|
dev_info(&pdev->dev, "llc on threshold (%u)\n", data->llc_on_threshold);
|
|
}
|
|
|
|
/* Get configs for each thermal zones */
|
|
data->num_amb_tz_configs = of_get_child_count(np);
|
|
data->amb_tz_config = kzalloc(sizeof(struct ambient_thermal_zone_config) *
|
|
data->num_amb_tz_configs, GFP_KERNEL);
|
|
|
|
for_each_available_child_of_node(np, child) {
|
|
struct device_node *trips, *tz_np;
|
|
struct ambient_thermal_zone_config *tz_config = &data->amb_tz_config[i++];
|
|
|
|
tz_np = of_parse_phandle(child, "tz", 0);
|
|
tz_config->tzd = thermal_zone_get_zone_by_name(tz_np->name);
|
|
|
|
dev_info(&pdev->dev, "Config for %s thermal zone\n", tz_config->tzd->type);
|
|
|
|
// set cpu hotplug
|
|
if (!of_property_read_string(child, "hotplug_cpu_list", &buf)) {
|
|
cpulist_parse(buf, &tz_config->cpu_domain);
|
|
dev_info(&pdev->dev, "Hotplug control for CPU %s\n", buf);
|
|
if (of_property_read_u32(child, "hotplug_in_threshold",
|
|
&tz_config->hotplug_in_threshold))
|
|
tz_config->hotplug_in_threshold = HOTPLUG_IN_THRESHOLD;
|
|
dev_info(&pdev->dev, "Hotplug in threshold (%u)\n",
|
|
tz_config->hotplug_in_threshold);
|
|
if (of_property_read_u32(child, "hotplug_out_threshold",
|
|
&tz_config->hotplug_out_threshold))
|
|
tz_config->hotplug_out_threshold = HOTPLUG_OUT_THRESHOLD;
|
|
dev_info(&pdev->dev, "Hotplug out threshold (%u)\n",
|
|
tz_config->hotplug_out_threshold);
|
|
}
|
|
|
|
// Change throttle temp of tz
|
|
trips = of_get_child_by_name(child, "trip_set");
|
|
if (trips) {
|
|
struct device_node *trip_child;
|
|
int j = 0;
|
|
|
|
tz_config->num_throttle_configs = of_get_child_count(trips);
|
|
tz_config->throttle_config =
|
|
kzalloc(sizeof(struct exynos_amb_throttle_config) *
|
|
tz_config->num_throttle_configs, GFP_KERNEL);
|
|
|
|
dev_info(&pdev->dev, "Trip conf for %s thermal zone\n", tz_np->name);
|
|
for_each_available_child_of_node (trips, trip_child) {
|
|
struct exynos_amb_throttle_config *throttle_conf =
|
|
&tz_config->throttle_config[j++];
|
|
|
|
dev_info(&pdev->dev, "#%u trip config\n", j);
|
|
if (of_property_read_u32(trip_child, "trigger_cond",
|
|
&throttle_conf->trigger_cond))
|
|
throttle_conf->trigger_cond = 2;
|
|
if (throttle_conf->trigger_cond > 2)
|
|
throttle_conf->trigger_cond = 2;
|
|
|
|
if (of_property_read_u32(trip_child, "trip_point",
|
|
&throttle_conf->trip_point))
|
|
throttle_conf->trigger_cond = 2;
|
|
dev_info(&pdev->dev, "trip point (%u)\n",
|
|
throttle_conf->trip_point);
|
|
|
|
if (of_property_read_u32(trip_child, "trigger_temp",
|
|
&throttle_conf->trigger_temp))
|
|
throttle_conf->trigger_cond = 2;
|
|
dev_info(&pdev->dev, "trigger temp (%u)\n",
|
|
throttle_conf->trigger_temp);
|
|
|
|
if (of_property_read_u32(trip_child, "release_temp",
|
|
&throttle_conf->release_temp))
|
|
throttle_conf->trigger_cond = 2;
|
|
dev_info(&pdev->dev, "release temp (%u)\n",
|
|
throttle_conf->release_temp);
|
|
|
|
if (of_property_read_u32(trip_child, "throttle_temp",
|
|
&throttle_conf->amb_throttle_temp))
|
|
throttle_conf->trigger_cond = 2;
|
|
dev_info(&pdev->dev, "throttle temp (%u)\n",
|
|
throttle_conf->amb_throttle_temp);
|
|
|
|
dev_info(&pdev->dev, "Trigger cond (%s)\n",
|
|
trigger_cond_desc[throttle_conf->trigger_cond]);
|
|
}
|
|
}
|
|
}
|
|
|
|
snprintf(data->cpuhp_req.name, THERMAL_NAME_LENGTH, "amb_cpuhp");
|
|
exynos_cpuhp_add_request(&data->cpuhp_req);
|
|
|
|
return 0;
|
|
}
|
|
static int exynos_amb_control_check_devs_dep(struct platform_device *pdev)
|
|
{
|
|
const char *buf;
|
|
struct device_node *np, *child;
|
|
|
|
np = pdev->dev.of_node;
|
|
|
|
/* Check dependency of all drivers */
|
|
if (!of_property_read_string(np, "amb_tz_name", &buf)) {
|
|
if (PTR_ERR(thermal_zone_get_zone_by_name(buf)) == -ENODEV) {
|
|
dev_err(&pdev->dev, "amb thermal zone is not registered!\n");
|
|
return -EPROBE_DEFER;
|
|
}
|
|
} else {
|
|
dev_err(&pdev->dev, "ambient thermal zone is not defined!\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
for_each_available_child_of_node(np, child) {
|
|
struct device_node *tz_np = of_parse_phandle(child, "tz", 0);
|
|
|
|
if (tz_np == NULL) {
|
|
dev_err(&pdev->dev, "thermal zone is not defined!\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (PTR_ERR(thermal_zone_get_zone_by_name(tz_np->name)) == -ENODEV) {
|
|
dev_err(&pdev->dev, "%s thermal zone is not registered!\n", tz_np->name);
|
|
return -EPROBE_DEFER;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_amb_control_pm_notify(struct notifier_block *nb,
|
|
unsigned long mode, void *_unused)
|
|
{
|
|
struct exynos_amb_control_data *data = container_of(nb,
|
|
struct exynos_amb_control_data, pm_notify);
|
|
|
|
switch (mode) {
|
|
case PM_HIBERNATION_PREPARE:
|
|
case PM_RESTORE_PREPARE:
|
|
case PM_SUSPEND_PREPARE:
|
|
mutex_lock(&data->lock);
|
|
data->in_suspend = true;
|
|
mutex_unlock(&data->lock);
|
|
kthread_cancel_delayed_work_sync(&data->amb_dwork);
|
|
break;
|
|
case PM_POST_HIBERNATION:
|
|
case PM_POST_RESTORE:
|
|
case PM_POST_SUSPEND:
|
|
mutex_lock(&data->lock);
|
|
data->in_suspend = false;
|
|
mutex_unlock(&data->lock);
|
|
exynos_amb_control_set_polling(data, data->current_sampling_rate);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_amb_control_probe(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = exynos_amb_control_check_devs_dep(pdev);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
amb_control = kzalloc(sizeof(struct exynos_amb_control_data), GFP_KERNEL);
|
|
platform_set_drvdata(pdev, amb_control);
|
|
|
|
ret = exynos_amb_control_parse_dt(pdev);
|
|
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to parse dt (%ld)\n", ret);
|
|
kfree(amb_control);
|
|
amb_control = NULL;
|
|
return ret;
|
|
}
|
|
|
|
mutex_init(&amb_control->lock);
|
|
|
|
exynos_amb_control_work_init(pdev);
|
|
|
|
amb_control->pm_notify.notifier_call = exynos_amb_control_pm_notify;
|
|
register_pm_notifier(&amb_control->pm_notify);
|
|
|
|
ret = sysfs_create_group(&pdev->dev.kobj, &exynos_amb_control_attr_group);
|
|
if (ret)
|
|
dev_err(&pdev->dev, "cannot create exynos amb control attr group");
|
|
|
|
dev_info(&pdev->dev, "Probe exynos amb controller successfully\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int exynos_amb_control_remove(struct platform_device *pdev)
|
|
{
|
|
struct exynos_amb_control_data *data = platform_get_drvdata(pdev);
|
|
|
|
mutex_lock(&data->lock);
|
|
|
|
data->amb_disabled = true;
|
|
|
|
mutex_unlock(&data->lock);
|
|
|
|
kthread_cancel_delayed_work_sync(&data->amb_dwork);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id exynos_amb_control_match[] = {
|
|
{ .compatible = "samsung,exynos-amb-control", },
|
|
{ /* sentinel */ },
|
|
};
|
|
|
|
static struct platform_driver exynos_amb_control_driver = {
|
|
.driver = {
|
|
.name = "exynos-amb-control",
|
|
.of_match_table = exynos_amb_control_match,
|
|
.suppress_bind_attrs = true,
|
|
},
|
|
.probe = exynos_amb_control_probe,
|
|
.remove = exynos_amb_control_remove,
|
|
};
|
|
module_platform_driver(exynos_amb_control_driver);
|
|
|
|
MODULE_DEVICE_TABLE(of, exynos_amb_control_match);
|
|
|
|
MODULE_AUTHOR("Hanjun Shin <hanjun.shin@samsung.com>");
|
|
MODULE_DESCRIPTION("EXYNOS AMB CONTROL Driver");
|
|
MODULE_LICENSE("GPL");
|