Orange Pi5 kernel

Deprecated Linux kernel 5.10.110 for OrangePi 5/5B/5+ boards

3 Commits   0 Branches   0 Tags
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd
 * Author: Finley Xiao <finley.xiao@rock-chips.com>
 */

#include <dt-bindings/soc/rockchip-system-status.h>
#include <linux/clk-provider.h>
#include <linux/cpu.h>
#include <linux/cpufreq.h>
#include <linux/devfreq.h>
#include <linux/device.h>
#include <linux/fb.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/notifier.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <linux/pm_qos.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/reboot.h>
#include <linux/slab.h>
#include <linux/suspend.h>
#include <linux/thermal.h>
#include <linux/uaccess.h>
#include <linux/version.h>
#include <linux/delay.h>
#include <soc/rockchip/rockchip_opp_select.h>
#include <soc/rockchip/rockchip_system_monitor.h>
#include <soc/rockchip/rockchip-system-status.h>

#include "../../gpu/drm/rockchip/ebc-dev/ebc_dev.h"
#include "../../opp/opp.h"
#include "../../regulator/internal.h"
#include "../../thermal/thermal_core.h"

#define CPU_REBOOT_FREQ		816000 /* kHz */
#define VIDEO_1080P_SIZE	(1920 * 1080)
#define THERMAL_POLLING_DELAY	200 /* milliseconds */

struct video_info {
	unsigned int width;
	unsigned int height;
	unsigned int ishevc;
	unsigned int videoFramerate;
	unsigned int streamBitrate;
	struct list_head node;
};

struct system_monitor_attr {
	struct attribute attr;
	ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
			char *buf);
	ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
			 const char *buf, size_t n);
};

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 last_temp;
	int offline_cpus_temp;
	int temp_hysteresis;
	unsigned int delay;
	bool is_temp_offline;
};

static unsigned long system_status;
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 DECLARE_RWSEM(mdev_list_sem);

static LIST_HEAD(video_info_list);
static LIST_HEAD(monitor_dev_list);
static struct system_monitor *system_monitor;
static atomic_t monitor_in_suspend;

static BLOCKING_NOTIFIER_HEAD(system_monitor_notifier_list);
static BLOCKING_NOTIFIER_HEAD(system_status_notifier_list);

int rockchip_register_system_status_notifier(struct notifier_block *nb)
{
	return blocking_notifier_chain_register(&system_status_notifier_list,
						nb);
}
EXPORT_SYMBOL(rockchip_register_system_status_notifier);

int rockchip_unregister_system_status_notifier(struct notifier_block *nb)
{
	return blocking_notifier_chain_unregister(&system_status_notifier_list,
						  nb);
}
EXPORT_SYMBOL(rockchip_unregister_system_status_notifier);

static int rockchip_system_status_notifier_call_chain(unsigned long val)
{
	int ret = blocking_notifier_call_chain(&system_status_notifier_list,
					       val, NULL);

	return notifier_to_errno(ret);
}

void rockchip_set_system_status(unsigned long status)
{
	unsigned long old_system_status;
	unsigned int single_status_offset;

	mutex_lock(&system_status_mutex);

	old_system_status = system_status;

	while (status) {
		single_status_offset = fls(status) - 1;
		status &= ~(1 << single_status_offset);
		if (ref_count[single_status_offset] == 0)
			system_status |= 1 << single_status_offset;
		ref_count[single_status_offset]++;
	}

	if (old_system_status != system_status)
		rockchip_system_status_notifier_call_chain(system_status);

	mutex_unlock(&system_status_mutex);
}
EXPORT_SYMBOL(rockchip_set_system_status);

void rockchip_clear_system_status(unsigned long status)
{
	unsigned long old_system_status;
	unsigned int single_status_offset;

	mutex_lock(&system_status_mutex);

	old_system_status = system_status;

	while (status) {
		single_status_offset = fls(status) - 1;
		status &= ~(1 << single_status_offset);
		if (ref_count[single_status_offset] == 0) {
			continue;
		} else {
			if (ref_count[single_status_offset] == 1)
				system_status &= ~(1 << single_status_offset);
			ref_count[single_status_offset]--;
		}
	}

	if (old_system_status != system_status)
		rockchip_system_status_notifier_call_chain(system_status);

	mutex_unlock(&system_status_mutex);
}
EXPORT_SYMBOL(rockchip_clear_system_status);

unsigned long rockchip_get_system_status(void)
{
	return system_status;
}
EXPORT_SYMBOL(rockchip_get_system_status);

int rockchip_add_system_status_interface(struct device *dev)
{
	if (!system_monitor || !system_monitor->kobj) {
		pr_err("failed to get system status kobj\n");
		return -EINVAL;
	}

	return compat_only_sysfs_link_entry_to_kobj(&dev->kobj,
						    system_monitor->kobj,
						    "system_status", NULL);
}
EXPORT_SYMBOL(rockchip_add_system_status_interface);

static unsigned long rockchip_get_video_param(char **str)
{
	char *p;
	unsigned long val = 0;

	strsep(str, "=");
	p = strsep(str, ",");
	if (p) {
		if (kstrtoul(p, 10, &val))
			return 0;
	}

	return val;
}

/*
 * format:
 * 0,width=val,height=val,ishevc=val,videoFramerate=val,streamBitrate=val
 * 1,width=val,height=val,ishevc=val,videoFramerate=val,streamBitrate=val
 */
static struct video_info *rockchip_parse_video_info(const char *buf)
{
	struct video_info *video_info;
	const char *cp = buf;
	char *str, *p;
	int ntokens = 0;

	while ((cp = strpbrk(cp + 1, ",")))
		ntokens++;
	if (ntokens != 5)
		return NULL;

	video_info = kzalloc(sizeof(*video_info), GFP_KERNEL);
	if (!video_info)
		return NULL;

	INIT_LIST_HEAD(&video_info->node);

	str = kstrdup(buf, GFP_KERNEL);
	p = str;
	strsep(&p, ",");
	video_info->width = rockchip_get_video_param(&p);
	video_info->height = rockchip_get_video_param(&p);
	video_info->ishevc = rockchip_get_video_param(&p);
	video_info->videoFramerate = rockchip_get_video_param(&p);
	video_info->streamBitrate = rockchip_get_video_param(&p);
	pr_debug("%c,width=%d,height=%d,ishevc=%d,videoFramerate=%d,streamBitrate=%d\n",
		 buf[0],
		 video_info->width,
		 video_info->height,
		 video_info->ishevc,
		 video_info->videoFramerate,
		 video_info->streamBitrate);
	kfree(str);

	return video_info;
}

static struct video_info *rockchip_find_video_info(const char *buf)
{
	struct video_info *info, *video_info;

	video_info = rockchip_parse_video_info(buf);

	if (!video_info)
		return NULL;

	mutex_lock(&video_info_mutex);
	list_for_each_entry(info, &video_info_list, node) {
		if (info->width == video_info->width &&
		    info->height == video_info->height &&
		    info->ishevc == video_info->ishevc &&
		    info->videoFramerate == video_info->videoFramerate &&
		    info->streamBitrate == video_info->streamBitrate) {
			mutex_unlock(&video_info_mutex);
			kfree(video_info);
			return info;
		}
	}

	mutex_unlock(&video_info_mutex);
	kfree(video_info);

	return NULL;
}

static void rockchip_add_video_info(struct video_info *video_info)
{
	if (video_info) {
		mutex_lock(&video_info_mutex);
		list_add(&video_info->node, &video_info_list);
		mutex_unlock(&video_info_mutex);
	}
}

static void rockchip_del_video_info(struct video_info *video_info)
{
	if (video_info) {
		mutex_lock(&video_info_mutex);
		list_del(&video_info->node);
		mutex_unlock(&video_info_mutex);
		kfree(video_info);
	}
}

static void rockchip_update_video_info(void)
{
	struct video_info *video_info;
	unsigned int max_res = 0, max_stream_bitrate = 0, res = 0;

	mutex_lock(&video_info_mutex);
	if (list_empty(&video_info_list)) {
		mutex_unlock(&video_info_mutex);
		rockchip_clear_system_status(SYS_STATUS_VIDEO);
		return;
	}

	list_for_each_entry(video_info, &video_info_list, node) {
		res = video_info->width * video_info->height;
		if (res > max_res)
			max_res = res;
		if (video_info->streamBitrate > max_stream_bitrate)
			max_stream_bitrate = video_info->streamBitrate;
	}
	mutex_unlock(&video_info_mutex);

	if (max_res <= VIDEO_1080P_SIZE) {
		rockchip_set_system_status(SYS_STATUS_VIDEO_1080P);
	} else {
		if (max_stream_bitrate == 10)
			rockchip_set_system_status(SYS_STATUS_VIDEO_4K_10B);
		else
			rockchip_set_system_status(SYS_STATUS_VIDEO_4K);
	}
}

void rockchip_update_system_status(const char *buf)
{
	struct video_info *video_info;

	if (!buf)
		return;

	switch (buf[0]) {
	case '0':
		/* clear video flag */
		video_info = rockchip_find_video_info(buf);
		if (video_info) {
			rockchip_del_video_info(video_info);
			rockchip_update_video_info();
		}
		break;
	case '1':
		/* set video flag */
		video_info = rockchip_parse_video_info(buf);
		if (video_info) {
			rockchip_add_video_info(video_info);
			rockchip_update_video_info();
		}
		break;
	case 'L':
		/* clear low power flag */
		rockchip_clear_system_status(SYS_STATUS_LOW_POWER);
		break;
	case 'l':
		/* set low power flag */
		rockchip_set_system_status(SYS_STATUS_LOW_POWER);
		break;
	case 'p':
		/* set performance flag */
		rockchip_set_system_status(SYS_STATUS_PERFORMANCE);
		break;
	case 'n':
		/* clear performance flag */
		rockchip_clear_system_status(SYS_STATUS_PERFORMANCE);
		break;
	case 'S':
		/* set video svep flag */
		rockchip_set_system_status(SYS_STATUS_VIDEO_SVEP);
		break;
	case 's':
		/* clear video svep flag */
		rockchip_clear_system_status(SYS_STATUS_VIDEO_SVEP);
		break;
	default:
		break;
	}
}
EXPORT_SYMBOL(rockchip_update_system_status);

static ssize_t status_show(struct kobject *kobj, struct kobj_attribute *attr,
			   char *buf)
{
	unsigned int status = rockchip_get_system_status();

	return sprintf(buf, "0x%x\n", status);
}

static ssize_t status_store(struct kobject *kobj, struct kobj_attribute *attr,
			    const char *buf, size_t n)
{
	if (!n)
		return -EINVAL;

	rockchip_update_system_status(buf);

	return n;
}

static struct system_monitor_attr status =
	__ATTR(system_status, 0644, status_show, status_store);

static int rockchip_get_temp_freq_table(struct device_node *np,
					char *porp_name,
					struct temp_freq_table **freq_table)
{
	struct temp_freq_table *table;
	const struct property *prop;
	int count, i;

	prop = of_find_property(np, porp_name, NULL);
	if (!prop)
		return -EINVAL;

	if (!prop->value)
		return -ENODATA;

	count = of_property_count_u32_elems(np, porp_name);
	if (count < 0)
		return -EINVAL;

	if (count % 2)
		return -EINVAL;

	table = kzalloc(sizeof(*table) * (count / 2 + 1), GFP_KERNEL);
	if (!table)
		return -ENOMEM;

	for (i = 0; i < count / 2; i++) {
		of_property_read_u32_index(np, porp_name, 2 * i,
					   &table[i].temp);
		of_property_read_u32_index(np, porp_name, 2 * i + 1,
					   &table[i].freq);
	}
	table[i].freq = UINT_MAX;
	*freq_table = table;

	return 0;
}

static int rockchip_get_adjust_volt_table(struct device_node *np,
					  char *porp_name,
					  struct volt_adjust_table **table)
{
	struct volt_adjust_table *volt_table;
	const struct property *prop;
	int count, i;

	prop = of_find_property(np, porp_name, NULL);
	if (!prop)
		return -EINVAL;

	if (!prop->value)
		return -ENODATA;

	count = of_property_count_u32_elems(np, porp_name);
	if (count < 0)
		return -EINVAL;

	if (count % 3)
		return -EINVAL;

	volt_table = kzalloc(sizeof(*volt_table) * (count / 3 + 1), GFP_KERNEL);
	if (!volt_table)
		return -ENOMEM;

	for (i = 0; i < count / 3; i++) {
		of_property_read_u32_index(np, porp_name, 3 * i,
					   &volt_table[i].min);
		of_property_read_u32_index(np, porp_name, 3 * i + 1,
					   &volt_table[i].max);
		of_property_read_u32_index(np, porp_name, 3 * i + 2,
					   &volt_table[i].volt);
	}
	volt_table[i].min = 0;
	volt_table[i].max = 0;
	volt_table[i].volt = INT_MAX;

	*table = volt_table;

	return 0;
}

static int rockchip_get_low_temp_volt(struct monitor_dev_info *info,
				      unsigned long rate, int *delta_volt)
{
	int i, ret = -EINVAL;
	unsigned int _rate = (unsigned int)(rate / 1000000);

	if (!info->low_temp_adjust_table)
		return ret;

	for (i = 0; info->low_temp_adjust_table[i].volt != INT_MAX; i++) {
		if (_rate >= info->low_temp_adjust_table[i].min &&
		    _rate <= info->low_temp_adjust_table[i].max) {
			*delta_volt = info->low_temp_adjust_table[i].volt;
			ret = 0;
		}
	}

	return ret;
}

static int rockchip_init_temp_opp_table(struct monitor_dev_info *info)
{
	struct device *dev = info->dev;
	struct opp_table *opp_table;
	struct dev_pm_opp *opp;
	int delta_volt = 0;
	int i = 0, max_count;
	unsigned long low_limit = 0, high_limit = 0;
	unsigned long low_limit_mem = 0, high_limit_mem = 0;
	bool reach_max_volt = false;
	bool reach_max_mem_volt = false;
	bool reach_high_temp_max_volt = false;
	bool reach_high_temp_max_mem_volt = false;

	max_count = dev_pm_opp_get_opp_count(dev);
	if (max_count <= 0)
		return max_count ? max_count : -ENODATA;
	info->opp_table = kzalloc(sizeof(*info->opp_table) * max_count,
				  GFP_KERNEL);
	if (!info->opp_table)
		return -ENOMEM;

	opp_table = dev_pm_opp_get_opp_table(dev);
	if (!opp_table) {
		kfree(info->opp_table);
		info->opp_table = NULL;
		return -ENOMEM;
	}
	mutex_lock(&opp_table->lock);
	list_for_each_entry(opp, &opp_table->opp_list, node) {
		if (!opp->available)
			continue;
		info->opp_table[i].rate = opp->rate;
		info->opp_table[i].volt = opp->supplies[0].u_volt;
		info->opp_table[i].max_volt = opp->supplies[0].u_volt_max;

		if (opp->supplies[0].u_volt <= info->high_temp_max_volt) {
			if (!reach_high_temp_max_volt)
				high_limit = opp->rate;
			if (opp->supplies[0].u_volt == info->high_temp_max_volt)
				reach_high_temp_max_volt = true;
		}

		if (rockchip_get_low_temp_volt(info, opp->rate, &delta_volt))
			delta_volt = 0;
		if ((opp->supplies[0].u_volt + delta_volt) <= info->max_volt) {
			info->opp_table[i].low_temp_volt =
				opp->supplies[0].u_volt + delta_volt;
			if (info->opp_table[i].low_temp_volt <
			    info->low_temp_min_volt)
				info->opp_table[i].low_temp_volt =
					info->low_temp_min_volt;
			if (!reach_max_volt)
				low_limit = opp->rate;
			if (info->opp_table[i].low_temp_volt == info->max_volt)
				reach_max_volt = true;
		} else {
			info->opp_table[i].low_temp_volt = info->max_volt;
		}
		if (low_limit && low_limit != opp->rate)
			info->low_limit = low_limit;
		if (high_limit && high_limit != opp->rate)
			info->high_limit = high_limit;

		if (opp_table->regulator_count > 1) {
			info->opp_table[i].mem_volt = opp->supplies[1].u_volt;
			info->opp_table[i].max_mem_volt = opp->supplies[1].u_volt_max;

			if (opp->supplies[1].u_volt <= info->high_temp_max_volt) {
				if (!reach_high_temp_max_mem_volt)
					high_limit_mem = opp->rate;
				if (opp->supplies[1].u_volt == info->high_temp_max_volt)
					reach_high_temp_max_mem_volt = true;
			}

			if ((opp->supplies[1].u_volt + delta_volt) <= info->max_volt) {
				info->opp_table[i].low_temp_mem_volt =
					opp->supplies[1].u_volt + delta_volt;
				if (info->opp_table[i].low_temp_mem_volt <
				    info->low_temp_min_volt)
					info->opp_table[i].low_temp_mem_volt =
						info->low_temp_min_volt;
				if (!reach_max_mem_volt)
					low_limit_mem = opp->rate;
				if (info->opp_table[i].low_temp_mem_volt == info->max_volt)
					reach_max_mem_volt = true;
			} else {
				info->opp_table[i].low_temp_mem_volt = info->max_volt;
			}

			if (low_limit_mem && low_limit_mem != opp->rate) {
				if (info->low_limit > low_limit_mem)
					info->low_limit = low_limit_mem;
			}
			if (high_limit_mem && high_limit_mem != opp->rate) {
				if (info->high_limit > high_limit_mem)
					info->high_limit = high_limit_mem;
			}
		}

		dev_dbg(dev, "rate=%lu, volt=%lu %lu low_temp_volt=%lu %lu\n",
			info->opp_table[i].rate, info->opp_table[i].volt,
			info->opp_table[i].mem_volt,
			info->opp_table[i].low_temp_volt,
			info->opp_table[i].low_temp_mem_volt);
		i++;
	}
	mutex_unlock(&opp_table->lock);

	dev_pm_opp_put_opp_table(opp_table);

	return 0;
}

static int monitor_device_parse_wide_temp_config(struct device_node *np,
						 struct monitor_dev_info *info)
{
	struct device *dev = info->dev;
	unsigned long high_temp_max_freq;
	int ret = 0;
	u32 value;

	np = of_parse_phandle(dev->of_node, "operating-points-v2", 0);
	if (!np)
		return -EINVAL;

	if (of_property_read_u32(np, "rockchip,max-volt", &value))
		info->max_volt = ULONG_MAX;
	else
		info->max_volt = value;
	of_property_read_u32(np, "rockchip,temp-hysteresis",
			     &info->temp_hysteresis);
	if (of_property_read_u32(np, "rockchip,low-temp", &info->low_temp))
		info->low_temp = INT_MIN;
	rockchip_get_adjust_volt_table(np, "rockchip,low-temp-adjust-volt",
				       &info->low_temp_adjust_table);
	if (!of_property_read_u32(np, "rockchip,low-temp-min-volt", &value))
		info->low_temp_min_volt = value;
	if (of_property_read_u32(np, "rockchip,high-temp", &info->high_temp))
		info->high_temp = INT_MAX;
	if (of_property_read_u32(np, "rockchip,high-temp-max-volt",
				 &value))
		info->high_temp_max_volt = ULONG_MAX;
	else
		info->high_temp_max_volt = value;
	rockchip_init_temp_opp_table(info);
	rockchip_get_temp_freq_table(np, "rockchip,temp-freq-table",
				     &info->high_limit_table);
	if (!info->high_limit_table)
		rockchip_get_temp_freq_table(np, "rockchip,high-temp-limit-table",
					     &info->high_limit_table);
	if (!info->high_limit_table) {
		if (!of_property_read_u32(np, "rockchip,high-temp-max-freq",
					  &value)) {
			high_temp_max_freq = value * 1000;
			if (info->high_limit)
				info->high_limit = min(high_temp_max_freq,
						       info->high_limit);
			else
				info->high_limit = high_temp_max_freq;
		}
	} else {
		info->high_limit = 0;
	}
	dev_info(dev, "l=%d h=%d hyst=%d l_limit=%lu h_limit=%lu h_table=%d\n",
		 info->low_temp, info->high_temp, info->temp_hysteresis,
		 info->low_limit, info->high_limit,
		 info->high_limit_table ? true : false);

	if ((info->low_temp + info->temp_hysteresis) > info->high_temp) {
		dev_err(dev, "Invalid temperature, low=%d high=%d hyst=%d\n",
			info->low_temp, info->high_temp,
			info->temp_hysteresis);
		ret = -EINVAL;
		goto err;
	}
	if (!info->low_temp_adjust_table && !info->low_temp_min_volt &&
	    !info->low_limit && !info->high_limit && !info->high_limit_table) {
		ret = -EINVAL;
		goto err;
	}
	if (info->low_temp_adjust_table || info->low_temp_min_volt)
		info->is_low_temp_enabled = true;

	return 0;
err:
	kfree(info->low_temp_adjust_table);
	info->low_temp_adjust_table = NULL;
	kfree(info->opp_table);
	info->opp_table = NULL;

	return ret;
}

static int monitor_device_parse_status_config(struct device_node *np,
					      struct monitor_dev_info *info)
{
	int ret;

	ret = of_property_read_u32(np, "rockchip,video-4k-freq",
				   &info->video_4k_freq);
	ret &= of_property_read_u32(np, "rockchip,reboot-freq",
				    &info->reboot_freq);
	if (info->devp->type == MONITOR_TPYE_CPU) {
		if (!info->reboot_freq) {
			info->reboot_freq = CPU_REBOOT_FREQ;
			ret = 0;
		}
	}

	return ret;
}

static int monitor_device_parse_early_min_volt(struct device_node *np,
					       struct monitor_dev_info *info)
{
	return of_property_read_u32(np, "rockchip,early-min-microvolt",
				    &info->early_min_volt);
}

static int monitor_device_parse_read_margin(struct device_node *np,
					    struct monitor_dev_info *info)
{
	if (of_property_read_bool(np, "volt-mem-read-margin"))
		return 0;
	return -EINVAL;
}

static int monitor_device_parse_scmi_clk(struct device_node *np,
					 struct monitor_dev_info *info)
{
	struct clk *clk;

	clk = clk_get(info->dev, NULL);
	if (strstr(__clk_get_name(clk), "scmi"))
		return 0;
	return -EINVAL;
}

static int monitor_device_parse_dt(struct device *dev,
				   struct monitor_dev_info *info)
{
	struct device_node *np;
	int ret;

	np = of_parse_phandle(dev->of_node, "operating-points-v2", 0);
	if (!np)
		return -EINVAL;

	of_property_read_u32(np, "rockchip,init-freq", &info->init_freq);

	ret = monitor_device_parse_wide_temp_config(np, info);
	ret &= monitor_device_parse_status_config(np, info);
	ret &= monitor_device_parse_early_min_volt(np, info);
	ret &= monitor_device_parse_read_margin(np, info);
	ret &= monitor_device_parse_scmi_clk(np, info);

	of_node_put(np);

	return ret;
}

int rockchip_monitor_cpu_low_temp_adjust(struct monitor_dev_info *info,
					 bool is_low)
{
	if (info->low_limit) {
		if (is_low)
			freq_qos_update_request(&info->max_temp_freq_req,
						info->low_limit / 1000);
		else
			freq_qos_update_request(&info->max_temp_freq_req,
						FREQ_QOS_MAX_DEFAULT_VALUE);
	}

	return 0;
}
EXPORT_SYMBOL(rockchip_monitor_cpu_low_temp_adjust);

int rockchip_monitor_cpu_high_temp_adjust(struct monitor_dev_info *info,
					  bool is_high)
{
	if (!info->high_limit)
		return 0;

	if (info->high_limit_table) {
		freq_qos_update_request(&info->max_temp_freq_req,
					info->high_limit / 1000);
		return 0;
	}

	if (is_high)
		freq_qos_update_request(&info->max_temp_freq_req,
					info->high_limit / 1000);
	else
		freq_qos_update_request(&info->max_temp_freq_req,
					FREQ_QOS_MAX_DEFAULT_VALUE);

	return 0;
}
EXPORT_SYMBOL(rockchip_monitor_cpu_high_temp_adjust);

int rockchip_monitor_dev_low_temp_adjust(struct monitor_dev_info *info,
					 bool is_low)
{
	if (!info->low_limit)
		return 0;

	if (is_low)
		dev_pm_qos_update_request(&info->dev_max_freq_req,
					  info->low_limit / 1000);
	else
		dev_pm_qos_update_request(&info->dev_max_freq_req,
					  PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE);

	return 0;
}
EXPORT_SYMBOL(rockchip_monitor_dev_low_temp_adjust);

int rockchip_monitor_dev_high_temp_adjust(struct monitor_dev_info *info,
					  bool is_high)
{
	if (!info->high_limit)
		return 0;

	if (info->high_limit_table) {
		dev_pm_qos_update_request(&info->dev_max_freq_req,
					  info->high_limit / 1000);
		return 0;
	}

	if (is_high)
		dev_pm_qos_update_request(&info->dev_max_freq_req,
					  info->high_limit / 1000);
	else
		dev_pm_qos_update_request(&info->dev_max_freq_req,
					  PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE);

	return 0;
}
EXPORT_SYMBOL(rockchip_monitor_dev_high_temp_adjust);

static int rockchip_adjust_low_temp_opp_volt(struct monitor_dev_info *info,
					     bool is_low_temp)
{
	struct device *dev = info->dev;
	struct opp_table *opp_table;
	struct dev_pm_opp *opp;
	int i = 0;

	opp_table = dev_pm_opp_get_opp_table(dev);
	if (!opp_table)
		return -ENOMEM;

	mutex_lock(&opp_table->lock);
	list_for_each_entry(opp, &opp_table->opp_list, node) {
		if (!opp->available)
			continue;
		if (is_low_temp) {
			if (opp->supplies[0].u_volt_max <
			    info->opp_table[i].low_temp_volt)
				opp->supplies[0].u_volt_max =
					info->opp_table[i].low_temp_volt;
			opp->supplies[0].u_volt =
				info->opp_table[i].low_temp_volt;
			opp->supplies[0].u_volt_min = opp->supplies[0].u_volt;
			if (opp_table->regulator_count > 1) {
				if (opp->supplies[1].u_volt_max <
				    info->opp_table[i].low_temp_mem_volt)
					opp->supplies[1].u_volt_max =
						info->opp_table[i].low_temp_mem_volt;
				opp->supplies[1].u_volt =
					info->opp_table[i].low_temp_mem_volt;
				opp->supplies[1].u_volt_min =
					opp->supplies[1].u_volt;
			}
		} else {
			opp->supplies[0].u_volt_min = info->opp_table[i].volt;
			opp->supplies[0].u_volt = opp->supplies[0].u_volt_min;
			opp->supplies[0].u_volt_max =
				info->opp_table[i].max_volt;
			if (opp_table->regulator_count > 1) {
				opp->supplies[1].u_volt_min =
					info->opp_table[i].mem_volt;
				opp->supplies[1].u_volt =
					opp->supplies[1].u_volt_min;
				opp->supplies[1].u_volt_max =
					info->opp_table[i].max_mem_volt;
			}
		}
		i++;
	}
	mutex_unlock(&opp_table->lock);

	dev_pm_opp_put_opp_table(opp_table);

	return 0;
}

static void rockchip_low_temp_adjust(struct monitor_dev_info *info,
				     bool is_low)
{
	struct monitor_dev_profile *devp = info->devp;
	int ret = 0;

	dev_dbg(info->dev, "low_temp %d\n", is_low);

	if (info->opp_table)
		rockchip_adjust_low_temp_opp_volt(info, is_low);

	if (devp->low_temp_adjust)
		ret = devp->low_temp_adjust(info, is_low);
	if (!ret)
		info->is_low_temp = is_low;

	if (devp->update_volt)
		devp->update_volt(info);
}

static void rockchip_high_temp_adjust(struct monitor_dev_info *info,
				      bool is_high)
{
	struct monitor_dev_profile *devp = info->devp;
	int ret = 0;

	if (!devp->high_temp_adjust)
		return;

	if (info->high_limit_table) {
		devp->high_temp_adjust(info, is_high);
	} else {
		dev_dbg(info->dev, "high_temp %d\n", is_high);
		ret = devp->high_temp_adjust(info, is_high);
		if (!ret)
			info->is_high_temp = is_high;
	}
}

int rockchip_monitor_suspend_low_temp_adjust(int cpu)
{
	struct monitor_dev_info *info = NULL, *tmp;

	list_for_each_entry(tmp, &monitor_dev_list, node) {
		if (tmp->devp->type != MONITOR_TPYE_CPU)
			continue;
		if (cpumask_test_cpu(cpu, &tmp->devp->allowed_cpus)) {
			info = tmp;
			break;
		}
	}

	if (!info || !info->is_low_temp_enabled)
		return 0;

	if (info->high_limit_table) {
		info->high_limit = 0;
		rockchip_high_temp_adjust(info, true);
	} else if (info->is_high_temp) {
		rockchip_high_temp_adjust(info, false);
	}
	if (!info->is_low_temp)
		rockchip_low_temp_adjust(info, true);

	return 0;
}
EXPORT_SYMBOL(rockchip_monitor_suspend_low_temp_adjust);

static int
rockchip_system_monitor_wide_temp_adjust(struct monitor_dev_info *info,
					 int temp)
{
	unsigned long target_freq = 0;
	int i;

	if (temp < info->low_temp) {
		if (!info->is_low_temp)
			rockchip_low_temp_adjust(info, true);
	} else if (temp > (info->low_temp + info->temp_hysteresis)) {
		if (info->is_low_temp)
			rockchip_low_temp_adjust(info, false);
	}

	if (info->high_limit_table) {
		for (i = 0; info->high_limit_table[i].freq != UINT_MAX; i++) {
			if (temp > info->high_limit_table[i].temp)
				target_freq =
					info->high_limit_table[i].freq * 1000;
		}
		if (target_freq != info->high_limit) {
			info->high_limit = target_freq;
			rockchip_high_temp_adjust(info, true);
		}
	} else {
		if (temp > info->high_temp) {
			if (!info->is_high_temp)
				rockchip_high_temp_adjust(info, true);
		} else if (temp < (info->high_temp - info->temp_hysteresis)) {
			if (info->is_high_temp)
				rockchip_high_temp_adjust(info, false);
		}
	}

	return 0;
}

static void
rockchip_system_monitor_wide_temp_init(struct monitor_dev_info *info)
{
	int ret, temp;

	if (!info->opp_table)
		return;

	/*
	 * set the init state to low temperature that the voltage will be enough
	 * when cpu up at low temperature.
	 */
	if (!info->is_low_temp) {
		if (info->opp_table)
			rockchip_adjust_low_temp_opp_volt(info, true);
		info->is_low_temp = true;
	}

	ret = thermal_zone_get_temp(system_monitor->tz, &temp);
	if (ret || temp == THERMAL_TEMP_INVALID) {
		dev_err(info->dev,
			"failed to read out thermal zone (%d)\n", ret);
		return;
	}

	if (temp > info->high_temp) {
		if (info->opp_table)
			rockchip_adjust_low_temp_opp_volt(info, false);
		info->is_low_temp = false;
		info->is_high_temp = true;
	} else if (temp > (info->low_temp + info->temp_hysteresis)) {
		if (info->opp_table)
			rockchip_adjust_low_temp_opp_volt(info, false);
		info->is_low_temp = false;
	}
}

static const char *get_rdev_name(struct regulator_dev *rdev)
{
	if (rdev->constraints && rdev->constraints->name)
		return rdev->constraints->name;
	else if (rdev->desc->name)
		return rdev->desc->name;
	else
		return "";
}

static void
rockchip_system_monitor_early_regulator_init(struct monitor_dev_info *info)
{
	struct regulator *reg;
	struct regulator_dev *rdev;

	if (!info->early_min_volt || !info->regulators)
		return;

	rdev = info->regulators[0]->rdev;
	reg = regulator_get(NULL, get_rdev_name(rdev));
	if (!IS_ERR_OR_NULL(reg)) {
		info->early_reg = reg;
		reg->voltage[PM_SUSPEND_ON].min_uV = info->early_min_volt;
		reg->voltage[PM_SUSPEND_ON].max_uV = rdev->constraints->max_uV;
	}
}

static int
rockchip_system_monitor_freq_qos_requset(struct monitor_dev_info *info)
{
	struct devfreq *devfreq;
	struct cpufreq_policy *policy;
	int max_default_value = FREQ_QOS_MAX_DEFAULT_VALUE;
	int ret;

	if (info->is_low_temp && info->low_limit)
		max_default_value = info->low_limit / 1000;
	else if (info->is_high_temp && info->high_limit)
		max_default_value = info->high_limit / 1000;

	if (info->devp->type == MONITOR_TPYE_CPU) {
		policy = (struct cpufreq_policy *)info->devp->data;
		ret = freq_qos_add_request(&policy->constraints,
					   &info->max_temp_freq_req,
					   FREQ_QOS_MAX,
					   max_default_value);
		if (ret < 0) {
			dev_info(info->dev,
				 "failed to add temp freq constraint\n");
			return ret;
		}
		ret = freq_qos_add_request(&policy->constraints,
					   &info->min_sta_freq_req,
					   FREQ_QOS_MIN,
					   FREQ_QOS_MIN_DEFAULT_VALUE);
		if (ret < 0) {
			dev_info(info->dev,
				 "failed to add sta freq constraint\n");
			freq_qos_remove_request(&info->max_temp_freq_req);
			return ret;
		}
		ret = freq_qos_add_request(&policy->constraints,
					   &info->max_sta_freq_req,
					   FREQ_QOS_MAX,
					   FREQ_QOS_MAX_DEFAULT_VALUE);
		if (ret < 0) {
			dev_info(info->dev,
				 "failed to add sta freq constraint\n");
			freq_qos_remove_request(&info->max_temp_freq_req);
			freq_qos_remove_request(&info->min_sta_freq_req);
			return ret;
		}
	} else if (info->devp->type == MONITOR_TPYE_DEV) {
		devfreq = (struct devfreq *)info->devp->data;
		ret = dev_pm_qos_add_request(devfreq->dev.parent,
					     &info->dev_max_freq_req,
					     DEV_PM_QOS_MAX_FREQUENCY,
					     max_default_value);
		if (ret < 0) {
			dev_info(info->dev, "failed to add freq constraint\n");
			return ret;
		}
	}

	return 0;
}

static int rockchip_system_monitor_parse_supplies(struct device *dev,
						  struct monitor_dev_info *info)
{
	struct opp_table *opp_table;
	struct dev_pm_set_opp_data *data;
	int len, count;

	opp_table = dev_pm_opp_get_opp_table(dev);
	if (IS_ERR(opp_table))
		return PTR_ERR(opp_table);

	if (opp_table->clk)
		info->clk = opp_table->clk;
	if (opp_table->regulators)
		info->regulators = opp_table->regulators;
	info->regulator_count = opp_table->regulator_count;

	if (opp_table->regulators && info->devp->set_opp) {
		count = opp_table->regulator_count;
		/* space for set_opp_data */
		len = sizeof(*data);
		/* space for old_opp.supplies and new_opp.supplies */
		len += 2 * sizeof(struct dev_pm_opp_supply) * count;
		data = kzalloc(len, GFP_KERNEL);
		if (!data)
			return -ENOMEM;
		data->old_opp.supplies = (void *)(data + 1);
		data->new_opp.supplies = data->old_opp.supplies + count;
		info->set_opp_data = data;
	}

	dev_pm_opp_put_opp_table(opp_table);

	return 0;
}

void rockchip_monitor_volt_adjust_lock(struct monitor_dev_info *info)
{
	if (info)
		mutex_lock(&info->volt_adjust_mutex);
}
EXPORT_SYMBOL(rockchip_monitor_volt_adjust_lock);

void rockchip_monitor_volt_adjust_unlock(struct monitor_dev_info *info)
{
	if (info)
		mutex_unlock(&info->volt_adjust_mutex);
}
EXPORT_SYMBOL(rockchip_monitor_volt_adjust_unlock);

static int rockchip_monitor_enable_opp_clk(struct device *dev,
					   struct rockchip_opp_info *opp_info)
{
	int ret = 0;

	if (!opp_info)
		return 0;

	ret = clk_bulk_prepare_enable(opp_info->num_clks, opp_info->clks);
	if (ret) {
		dev_err(dev, "failed to enable opp clks\n");
		return ret;
	}

	return 0;
}

static void rockchip_monitor_disable_opp_clk(struct device *dev,
					     struct rockchip_opp_info *opp_info)
{
	if (!opp_info)
		return;

	clk_bulk_disable_unprepare(opp_info->num_clks, opp_info->clks);
}

static int rockchip_monitor_set_opp(struct monitor_dev_info *info,
				    unsigned long old_freq,
				    unsigned long freq,
				    struct dev_pm_opp_supply *old_supply,
				    struct dev_pm_opp_supply *new_supply)
{
	struct dev_pm_set_opp_data *data;
	int size;

	data = info->set_opp_data;
	data->regulators = info->regulators;
	data->regulator_count = info->regulator_count;
	data->clk = info->clk;
	data->dev = info->dev;

	data->old_opp.rate = old_freq;
	size = sizeof(*old_supply) * info->regulator_count;
	if (!old_supply)
		memset(data->old_opp.supplies, 0, size);
	else
		memcpy(data->old_opp.supplies, old_supply, size);

	data->new_opp.rate = freq;
	memcpy(data->new_opp.supplies, new_supply, size);

	return info->devp->set_opp(data);
}

int rockchip_monitor_check_rate_volt(struct monitor_dev_info *info)
{
	struct device *dev = info->dev;
	struct regulator *vdd_reg = NULL;
	struct regulator *mem_reg = NULL;
	struct rockchip_opp_info *opp_info = info->devp->opp_info;
	struct dev_pm_opp *opp;
	unsigned long old_rate, new_rate, new_volt, new_mem_volt;
	int old_volt, old_mem_volt;
	u32 target_rm = UINT_MAX;
	bool is_set_clk = true;
	bool is_set_rm = false;
	int ret = 0;

	if (!info->regulators || !info->clk)
		return 0;

	mutex_lock(&info->volt_adjust_mutex);

	vdd_reg = info->regulators[0];
	old_rate = clk_get_rate(info->clk);
	old_volt = regulator_get_voltage(vdd_reg);
	if (info->regulator_count > 1) {
		mem_reg = info->regulators[1];
		old_mem_volt = regulator_get_voltage(mem_reg);
	}

	if (info->init_freq) {
		new_rate = info->init_freq * 1000;
		info->init_freq = 0;
	} else {
		new_rate = old_rate;
	}
	opp = dev_pm_opp_find_freq_ceil(dev, &new_rate);
	if (IS_ERR(opp)) {
		opp = dev_pm_opp_find_freq_floor(dev, &new_rate);
		if (IS_ERR(opp)) {
			ret = PTR_ERR(opp);
			goto unlock;
		}
	}
	new_volt = opp->supplies[0].u_volt;
	if (info->regulator_count > 1)
		new_mem_volt = opp->supplies[1].u_volt;
	dev_pm_opp_put(opp);

	if (old_rate == new_rate) {
		if (info->regulator_count > 1) {
			if (old_volt == new_volt &&
			    new_mem_volt == old_mem_volt)
				goto unlock;
		} else if (old_volt == new_volt) {
			goto unlock;
		}
	}
	if (!new_volt || (info->regulator_count > 1 && !new_mem_volt))
		goto unlock;

	if (info->devp->set_opp) {
		ret = rockchip_monitor_set_opp(info, old_rate, new_rate,
					       NULL, opp->supplies);
		goto unlock;
	}

	if (opp_info && opp_info->data && opp_info->data->set_read_margin) {
		is_set_rm = true;
		if (info->devp->type == MONITOR_TPYE_DEV) {
			if (!pm_runtime_active(dev)) {
				is_set_rm = false;
				if (opp_info->scmi_clk)
					is_set_clk = false;
			}
		}
	}
	rockchip_monitor_enable_opp_clk(dev, opp_info);
	rockchip_get_read_margin(dev, opp_info, new_volt, &target_rm);

	dev_dbg(dev, "%s: %lu Hz --> %lu Hz\n", __func__, old_rate, new_rate);
	if (new_rate >= old_rate) {
		rockchip_set_intermediate_rate(dev, opp_info, info->clk,
					       old_rate, new_rate,
					       true, is_set_clk);
		if (info->regulator_count > 1) {
			ret = regulator_set_voltage(mem_reg, new_mem_volt,
						    INT_MAX);
			if (ret) {
				dev_err(dev, "%s: failed to set volt: %lu\n",
					__func__, new_mem_volt);
				goto restore_voltage;
			}
		}
		ret = regulator_set_voltage(vdd_reg, new_volt, INT_MAX);
		if (ret) {
			dev_err(dev, "%s: failed to set volt: %lu\n",
				__func__, new_volt);
			goto restore_voltage;
		}
		rockchip_set_read_margin(dev, opp_info, target_rm, is_set_rm);
		if (is_set_clk && clk_set_rate(info->clk, new_rate)) {
			dev_err(dev, "%s: failed to set clock rate: %lu\n",
				__func__, new_rate);
			goto restore_rm;
		}
	} else {
		rockchip_set_intermediate_rate(dev, opp_info, info->clk,
					       old_rate, new_rate,
					       false, is_set_clk);
		rockchip_set_read_margin(dev, opp_info, target_rm, is_set_rm);
		if (is_set_clk && clk_set_rate(info->clk, new_rate)) {
			dev_err(dev, "%s: failed to set clock rate: %lu\n",
				__func__, new_rate);
			goto restore_rm;
		}
		ret = regulator_set_voltage(vdd_reg, new_volt,
					    INT_MAX);
		if (ret) {
			dev_err(dev, "%s: failed to set volt: %lu\n",
				__func__, new_volt);
			goto restore_freq;
		}
		if (info->regulator_count > 1) {
			ret = regulator_set_voltage(mem_reg, new_mem_volt,
						    INT_MAX);
			if (ret) {
				dev_err(dev, "%s: failed to set volt: %lu\n",
					__func__, new_mem_volt);
				goto restore_freq;
			}
		}
	}
	goto disable_clk;

restore_freq:
	if (is_set_clk && clk_set_rate(info->clk, old_rate))
		dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n",
			__func__, old_rate);
restore_rm:
	rockchip_get_read_margin(dev, opp_info, old_volt, &target_rm);
	rockchip_set_read_margin(dev, opp_info, target_rm, is_set_rm);
restore_voltage:
	if (info->regulator_count > 1)
		regulator_set_voltage(mem_reg, old_mem_volt, INT_MAX);
	regulator_set_voltage(vdd_reg, old_volt, INT_MAX);
disable_clk:
	rockchip_monitor_disable_opp_clk(dev, opp_info);
unlock:
	mutex_unlock(&info->volt_adjust_mutex);

	return ret;
}
EXPORT_SYMBOL(rockchip_monitor_check_rate_volt);

struct monitor_dev_info *
rockchip_system_monitor_register(struct device *dev,
				 struct monitor_dev_profile *devp)
{
	struct monitor_dev_info *info;

	if (!system_monitor)
		return ERR_PTR(-ENOMEM);

	info = kzalloc(sizeof(*info), GFP_KERNEL);
	if (!info)
		return ERR_PTR(-ENOMEM);
	info->dev = dev;
	info->devp = devp;

	mutex_init(&info->volt_adjust_mutex);

	rockchip_system_monitor_parse_supplies(dev, info);
	if (monitor_device_parse_dt(dev, info)) {
		rockchip_monitor_check_rate_volt(info);
		devp->is_checked = true;
		kfree(info->set_opp_data);
		kfree(info);
		return ERR_PTR(-EINVAL);
	}

	rockchip_system_monitor_early_regulator_init(info);
	rockchip_system_monitor_wide_temp_init(info);
	rockchip_monitor_check_rate_volt(info);
	devp->is_checked = true;
	rockchip_system_monitor_freq_qos_requset(info);

	down_write(&mdev_list_sem);
	list_add(&info->node, &monitor_dev_list);
	up_write(&mdev_list_sem);

	return info;
}
EXPORT_SYMBOL(rockchip_system_monitor_register);

void rockchip_system_monitor_unregister(struct monitor_dev_info *info)
{
	if (!info)
		return;

	down_write(&mdev_list_sem);
	list_del(&info->node);
	up_write(&mdev_list_sem);

	if (info->devp->type == MONITOR_TPYE_CPU) {
		freq_qos_remove_request(&info->max_temp_freq_req);
		freq_qos_remove_request(&info->min_sta_freq_req);
		freq_qos_remove_request(&info->max_sta_freq_req);
	} else {
		dev_pm_qos_remove_request(&info->dev_max_freq_req);
	}

	kfree(info->low_temp_adjust_table);
	kfree(info->opp_table);
	kfree(info->set_opp_data);
	kfree(info);
}
EXPORT_SYMBOL(rockchip_system_monitor_unregister);

int rockchip_system_monitor_register_notifier(struct notifier_block *nb)
{
	return blocking_notifier_chain_register(&system_monitor_notifier_list, nb);
}
EXPORT_SYMBOL(rockchip_system_monitor_register_notifier);

void rockchip_system_monitor_unregister_notifier(struct notifier_block *nb)
{
	blocking_notifier_chain_unregister(&system_monitor_notifier_list, nb);
}
EXPORT_SYMBOL(rockchip_system_monitor_unregister_notifier);

static int rockchip_system_monitor_temp_notify(int temp)
{
	struct system_monitor_event_data event_data;
	int ret;

	event_data.temp = temp;
	ret = blocking_notifier_call_chain(&system_monitor_notifier_list,
					   SYSTEM_MONITOR_CHANGE_TEMP,
					   (void *)&event_data);

	return notifier_to_errno(ret);
}

static int notify_dummy(struct thermal_zone_device *tz, int trip)
{
	return 0;
}

static struct thermal_governor thermal_gov_dummy = {
	.name		= "dummy",
	.throttle	= notify_dummy,
};

static int rockchip_system_monitor_parse_dt(struct system_monitor *monitor)
{
	struct device_node *np = monitor->dev->of_node;
	const char *tz_name, *buf = NULL;

	if (of_property_read_string(np, "rockchip,video-4k-offline-cpus", &buf))
		cpumask_clear(&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);

	if (of_find_property(np, "rockchip,thermal-governor-dummy", NULL)) {
		if (monitor->tz->governor->unbind_from_tz)
			monitor->tz->governor->unbind_from_tz(monitor->tz);
		monitor->tz->governor = &thermal_gov_dummy;
	}

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;

	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))
			remove_cpu(cpu);
	}

	cpumask_clear(&online_cpus);
	cpumask_andnot(&online_cpus, cpu_possible_mask,
		       &system_monitor->offline_cpus);
	cpumask_xor(&online_cpus, cpu_online_mask, &online_cpus);
	if (cpumask_empty(&online_cpus))
		goto out;
	for_each_cpu(cpu, &online_cpus)
		add_cpu(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;
	struct monitor_dev_info *info;

	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);

	if (temp < system_monitor->last_temp &&
	    system_monitor->last_temp - temp <= 2000)
		goto out;
	system_monitor->last_temp = temp;

	rockchip_system_monitor_temp_notify(temp);

	down_read(&mdev_list_sem);
	list_for_each_entry(info, &monitor_dev_list, node)
		rockchip_system_monitor_wide_temp_adjust(info, temp);
	up_read(&mdev_list_sem);

	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)
{
	if (atomic_read(&monitor_in_suspend))
		return;

	rockchip_system_monitor_thermal_update();
}

static void rockchip_system_status_cpu_limit_freq(struct monitor_dev_info *info,
						  unsigned long status)
{
	unsigned int target_freq = 0;

	if (status & SYS_STATUS_REBOOT) {
		freq_qos_update_request(&info->max_sta_freq_req,
					info->reboot_freq);
		freq_qos_update_request(&info->min_sta_freq_req,
					info->reboot_freq);
		return;
	}

	if (info->video_4k_freq && (status & SYS_STATUS_VIDEO_4K))
		target_freq = info->video_4k_freq;

	if (target_freq == info->status_max_limit)
		return;
	info->status_max_limit = target_freq;
	if (info->status_max_limit)
		freq_qos_update_request(&info->max_sta_freq_req,
					info->status_max_limit);
	else
		freq_qos_update_request(&info->max_sta_freq_req,
					FREQ_QOS_MAX_DEFAULT_VALUE);
}

static void rockchip_system_status_limit_freq(unsigned long status)
{
	struct monitor_dev_info *info;

	down_read(&mdev_list_sem);
	list_for_each_entry(info, &monitor_dev_list, node) {
		if (info->devp->type == MONITOR_TPYE_CPU)
			rockchip_system_status_cpu_limit_freq(info, status);
	}
	up_read(&mdev_list_sem);
}

static void rockchip_system_status_cpu_on_off(unsigned long status)
{
	struct cpumask offline_cpus;

	if (cpumask_empty(&system_monitor->video_4k_offline_cpus))
		return;

	cpumask_clear(&offline_cpus);
	if (status & SYS_STATUS_VIDEO_4K)
		cpumask_copy(&offline_cpus,
			     &system_monitor->video_4k_offline_cpus);
	if (cpumask_equal(&offline_cpus, &system_monitor->status_offline_cpus))
		return;
	cpumask_copy(&system_monitor->status_offline_cpus, &offline_cpus);
	rockchip_system_monitor_cpu_on_off();
}

static int rockchip_system_status_notifier(struct notifier_block *nb,
					   unsigned long status,
					   void *ptr)
{
	rockchip_system_status_limit_freq(status);

	rockchip_system_status_cpu_on_off(status);

	return NOTIFY_OK;
}

static int rockchip_system_monitor_set_cpu_uevent_suppress(bool is_suppress)
{
	struct monitor_dev_info *info;
	struct cpufreq_policy *policy;

	list_for_each_entry(info, &monitor_dev_list, node) {
		if (info->devp->type != MONITOR_TPYE_CPU)
			continue;
		policy = (struct cpufreq_policy *)info->devp->data;
		if (!policy || !policy->cdev)
			continue;
		if (is_suppress)
			dev_set_uevent_suppress(&policy->cdev->device, 1);
		else
			dev_set_uevent_suppress(&policy->cdev->device, 0);
	}

	return 0;
}

static int monitor_pm_notify(struct notifier_block *nb,
			     unsigned long mode, void *_unused)
{
	switch (mode) {
	case PM_HIBERNATION_PREPARE:
	case PM_RESTORE_PREPARE:
	case PM_SUSPEND_PREPARE:
		atomic_set(&monitor_in_suspend, 1);
		rockchip_system_monitor_set_cpu_uevent_suppress(true);
		break;
	case PM_POST_HIBERNATION:
	case PM_POST_RESTORE:
	case PM_POST_SUSPEND:
		if (system_monitor->tz)
			rockchip_system_monitor_thermal_update();
		atomic_set(&monitor_in_suspend, 0);
		rockchip_system_monitor_set_cpu_uevent_suppress(false);
		system_monitor->last_temp = INT_MAX;
		break;
	default:
		break;
	}
	return 0;
}

static struct notifier_block monitor_pm_nb = {
	.notifier_call = monitor_pm_notify,
};

static int rockchip_monitor_reboot_notifier(struct notifier_block *nb,
					     unsigned long action, void *ptr)
{
	rockchip_set_system_status(SYS_STATUS_REBOOT);
	if (system_monitor->tz)
		cancel_delayed_work_sync(&system_monitor->thermal_work);

	return NOTIFY_OK;
}

static struct notifier_block rockchip_monitor_reboot_nb = {
	.notifier_call = rockchip_monitor_reboot_notifier,
};

static int rockchip_monitor_fb_notifier(struct notifier_block *nb,
					unsigned long action, void *ptr)
{
	struct fb_event *event = ptr;

	if (action != FB_EVENT_BLANK)
		return NOTIFY_OK;

	switch (*((int *)event->data)) {
	case FB_BLANK_UNBLANK:
		rockchip_clear_system_status(SYS_STATUS_SUSPEND);
		break;
	case FB_BLANK_POWERDOWN:
		rockchip_set_system_status(SYS_STATUS_SUSPEND);
		break;
	default:
		break;
	}

	return NOTIFY_OK;
}

static struct notifier_block rockchip_monitor_fb_nb = {
	.notifier_call = rockchip_monitor_fb_notifier,
};

static int rockchip_eink_devfs_notifier(struct notifier_block *nb,
					unsigned long action, void *ptr)
{
	switch (action) {
	case EBC_ON:
		rockchip_clear_system_status(SYS_STATUS_LOW_POWER);
		break;
	case EBC_OFF:
		rockchip_set_system_status(SYS_STATUS_LOW_POWER);
		break;
	default:
		break;
	}

	return NOTIFY_OK;
}

static struct notifier_block rockchip_monitor_ebc_nb = {
	.notifier_call = rockchip_eink_devfs_notifier,
};

static void system_monitor_early_min_volt_function(struct work_struct *work)
{
	struct monitor_dev_info *info;
	struct regulator_dev *rdev;
	int min_uV, max_uV;
	int ret;

	down_read(&mdev_list_sem);
	list_for_each_entry(info, &monitor_dev_list, node) {
		if (!info->early_min_volt || !info->early_reg)
			continue;
		rdev = info->early_reg->rdev;
		min_uV = rdev->constraints->min_uV;
		max_uV = rdev->constraints->max_uV;
		ret = regulator_set_voltage(info->early_reg, min_uV, max_uV);
		if (ret)
			dev_err(&rdev->dev,
				"%s: failed to set volt\n", __func__);
		regulator_put(info->early_reg);
	}
	up_read(&mdev_list_sem);
}

static DECLARE_DELAYED_WORK(system_monitor_early_min_volt_work,
			    system_monitor_early_min_volt_function);

static int rockchip_system_monitor_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;

	system_monitor = devm_kzalloc(dev, sizeof(struct system_monitor),
				      GFP_KERNEL);
	if (!system_monitor)
		return -ENOMEM;
	system_monitor->dev = dev;

	system_monitor->kobj = kobject_create_and_add("system_monitor", NULL);
	if (!system_monitor->kobj)
		return -ENOMEM;
	if (sysfs_create_file(system_monitor->kobj, &status.attr))
		dev_err(dev, "failed to create system status sysfs\n");

	cpumask_clear(&system_monitor->status_offline_cpus);
	cpumask_clear(&system_monitor->offline_cpus);

	rockchip_system_monitor_parse_dt(system_monitor);
	if (system_monitor->tz) {
		system_monitor->last_temp = INT_MAX;
		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;
	rockchip_register_system_status_notifier(&system_monitor->status_nb);

	if (register_pm_notifier(&monitor_pm_nb))
		dev_err(dev, "failed to register suspend notifier\n");

	register_reboot_notifier(&rockchip_monitor_reboot_nb);

	if (fb_register_client(&rockchip_monitor_fb_nb))
		dev_err(dev, "failed to register fb nb\n");

	ebc_register_notifier(&rockchip_monitor_ebc_nb);

	schedule_delayed_work(&system_monitor_early_min_volt_work,
			      msecs_to_jiffies(30000));

	dev_info(dev, "system monitor probe\n");

	return 0;
}

static const struct of_device_id rockchip_system_monitor_of_match[] = {
	{
		.compatible = "rockchip,system-monitor",
	},
	{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rockchip_system_monitor_of_match);

static struct platform_driver rockchip_system_monitor_driver = {
	.probe	= rockchip_system_monitor_probe,
	.driver = {
		.name	= "rockchip-system-monitor",
		.of_match_table = rockchip_system_monitor_of_match,
	},
};
module_platform_driver(rockchip_system_monitor_driver);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Finley Xiao <finley.xiao@rock-chips.com>");
MODULE_DESCRIPTION("rockchip system monitor driver");