diff --git a/Documentation/devicetree/bindings/soc/rockchip/rockchip_system_monitor.txt b/Documentation/devicetree/bindings/soc/rockchip/rockchip_system_monitor.txt index 2f97e5b4cde0..2cdd8842327b 100644 --- a/Documentation/devicetree/bindings/soc/rockchip/rockchip_system_monitor.txt +++ b/Documentation/devicetree/bindings/soc/rockchip/rockchip_system_monitor.txt @@ -8,11 +8,25 @@ Required properties: Optional properties: - rockchip,video-4k-offline-cpus: A string containing cpus which will be killed when play 4k video. +- rockchip,thermal-zone: A thermal zone node containing thermal sensor, + it's used to get the current temperature. +- rockchip,polling-delay: The maximum number of milliseconds to wait + between polls. +- rockchip,offline-cpu-temp: An integer indicating the trip temperature level. +- rockchip,temp-hysteresis: A low hysteresis value on rockchip,offline-cpu-temp + property (above). +- rockchip,temp-offline-cpus: A string containing cpus which will be killed + when temperature is high. Example: system-monitor { compatible = "rockchip,system-monitor"; + rockchip,thermal-zone = "soc-thermal"; + rockchip,polling-delay = <200>; /* milliseconds */ + rockchip,offline-cpu-temp = <110000>; /* millicelsius */ + rockchip,temp-offline-cpus = "2-3"; + rockchip,video-4k-offline-cpus = "2-3"; }; diff --git a/drivers/soc/rockchip/rockchip_system_monitor.c b/drivers/soc/rockchip/rockchip_system_monitor.c index 2c184e93379f..67186df3435c 100644 --- a/drivers/soc/rockchip/rockchip_system_monitor.c +++ b/drivers/soc/rockchip/rockchip_system_monitor.c @@ -14,9 +14,11 @@ #include #include #include +#include #include #define VIDEO_1080P_SIZE (1920 * 1080) +#define THERMAL_POLLING_DELAY 200 /* milliseconds */ struct video_info { unsigned int width; @@ -39,9 +41,17 @@ struct system_monitor { struct device *dev; struct cpumask video_4k_offline_cpus; struct cpumask status_offline_cpus; + struct cpumask temp_offline_cpus; struct cpumask offline_cpus; struct notifier_block status_nb; struct kobject *kobj; + + struct thermal_zone_device *tz; + struct delayed_work thermal_work; + int offline_cpus_temp; + int temp_hysteresis; + unsigned int delay; + bool is_temp_offline; }; static unsigned long system_status; @@ -49,6 +59,7 @@ static unsigned long ref_count[32] = {0}; static DEFINE_MUTEX(system_status_mutex); static DEFINE_MUTEX(video_info_mutex); +static DEFINE_MUTEX(cpu_on_off_mutex); static LIST_HEAD(video_info_list); static struct system_monitor *system_monitor; @@ -354,26 +365,58 @@ static struct system_monitor_attr status = static int rockchip_system_monitor_parse_dt(struct system_monitor *monitor) { struct device_node *np = monitor->dev->of_node; - const char *buf = NULL; + const char *tz_name, *buf = NULL; - if (!of_property_read_string(np, "rockchip,video-4k-offline-cpus", - &buf)) + if (of_property_read_string(np, "rockchip,video-4k-offline-cpus", &buf)) + cpumask_clear(&system_monitor->video_4k_offline_cpus); + else cpulist_parse(buf, &monitor->video_4k_offline_cpus); + if (of_property_read_string(np, "rockchip,thermal-zone", &tz_name)) + goto out; + monitor->tz = thermal_zone_get_zone_by_name(tz_name); + if (IS_ERR(monitor->tz)) { + monitor->tz = NULL; + goto out; + } + if (of_property_read_u32(np, "rockchip,polling-delay", + &monitor->delay)) + monitor->delay = THERMAL_POLLING_DELAY; + + if (of_property_read_string(np, "rockchip,temp-offline-cpus", + &buf)) + cpumask_clear(&system_monitor->temp_offline_cpus); + else + cpulist_parse(buf, &system_monitor->temp_offline_cpus); + + if (of_property_read_u32(np, "rockchip,offline-cpu-temp", + &system_monitor->offline_cpus_temp)) + system_monitor->offline_cpus_temp = INT_MAX; + of_property_read_u32(np, "rockchip,temp-hysteresis", + &system_monitor->temp_hysteresis); +out: return 0; } static void rockchip_system_monitor_cpu_on_off(void) { #ifdef CONFIG_HOTPLUG_CPU + struct cpumask online_cpus, offline_cpus; unsigned int cpu; - struct cpumask online_cpus; - if (cpumask_equal(&system_monitor->offline_cpus, - &system_monitor->status_offline_cpus)) - return; - cpumask_copy(&system_monitor->offline_cpus, - &system_monitor->status_offline_cpus); + mutex_lock(&cpu_on_off_mutex); + + cpumask_clear(&offline_cpus); + if (system_monitor->is_temp_offline) { + cpumask_or(&offline_cpus, &system_monitor->status_offline_cpus, + &system_monitor->temp_offline_cpus); + } else { + cpumask_copy(&offline_cpus, + &system_monitor->status_offline_cpus); + } + if (cpumask_equal(&offline_cpus, &system_monitor->offline_cpus)) + goto out; + cpumask_copy(&system_monitor->offline_cpus, &offline_cpus); for_each_cpu(cpu, &system_monitor->offline_cpus) { if (cpu_online(cpu)) cpu_down(cpu); @@ -384,12 +427,58 @@ static void rockchip_system_monitor_cpu_on_off(void) &system_monitor->offline_cpus); cpumask_xor(&online_cpus, cpu_online_mask, &online_cpus); if (cpumask_empty(&online_cpus)) - return; + goto out; for_each_cpu(cpu, &online_cpus) cpu_up(cpu); + +out: + mutex_unlock(&cpu_on_off_mutex); #endif } +static void rockchip_system_monitor_temp_cpu_on_off(int temp) +{ + bool is_temp_offline; + + if (cpumask_empty(&system_monitor->temp_offline_cpus)) + return; + + if (temp > system_monitor->offline_cpus_temp) + is_temp_offline = true; + else if (temp < system_monitor->offline_cpus_temp - + system_monitor->temp_hysteresis) + is_temp_offline = false; + else + return; + + if (system_monitor->is_temp_offline == is_temp_offline) + return; + system_monitor->is_temp_offline = is_temp_offline; + rockchip_system_monitor_cpu_on_off(); +} + +static void rockchip_system_monitor_thermal_update(void) +{ + int temp, ret; + + ret = thermal_zone_get_temp(system_monitor->tz, &temp); + if (ret || temp == THERMAL_TEMP_INVALID) + goto out; + + dev_dbg(system_monitor->dev, "temperature=%d\n", temp); + + rockchip_system_monitor_temp_cpu_on_off(temp); + +out: + mod_delayed_work(system_freezable_wq, &system_monitor->thermal_work, + msecs_to_jiffies(system_monitor->delay)); +} + +static void rockchip_system_monitor_thermal_check(struct work_struct *work) +{ + rockchip_system_monitor_thermal_update(); +} + static void rockchip_system_status_cpu_on_off(unsigned long status) { struct cpumask offline_cpus; @@ -432,11 +521,17 @@ static int rockchip_system_monitor_probe(struct platform_device *pdev) if (sysfs_create_file(system_monitor->kobj, &status.attr)) dev_err(dev, "failed to create system status sysfs\n"); - cpumask_clear(&system_monitor->video_4k_offline_cpus); cpumask_clear(&system_monitor->status_offline_cpus); cpumask_clear(&system_monitor->offline_cpus); rockchip_system_monitor_parse_dt(system_monitor); + if (system_monitor->tz) { + INIT_DELAYED_WORK(&system_monitor->thermal_work, + rockchip_system_monitor_thermal_check); + mod_delayed_work(system_freezable_wq, + &system_monitor->thermal_work, + msecs_to_jiffies(system_monitor->delay)); + } system_monitor->status_nb.notifier_call = rockchip_system_status_notifier;