// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 Fuzhou Rockchip Electronics Co., Ltd * Author: Finley Xiao */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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) { struct video_info *info, *tmp; if (!video_info) return; mutex_lock(&video_info_mutex); list_for_each_entry_safe(info, tmp, &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) { list_del(&info->node); kfree(info); break; } } kfree(video_info); mutex_unlock(&video_info_mutex); } static void rockchip_update_video_info(void) { struct video_info *video_info; unsigned int max_res = 0, max_stream_bitrate = 0, res = 0; unsigned int max_video_framerate = 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; if (video_info->videoFramerate > max_video_framerate) max_video_framerate = video_info->videoFramerate; } 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); if (max_video_framerate == 60) rockchip_set_system_status(SYS_STATUS_VIDEO_4K_60P); 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_parse_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_TYPE_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) return 0; if (!freq_qos_request_active(&info->max_temp_freq_req)) return 0; 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 (!freq_qos_request_active(&info->max_temp_freq_req)) 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 (!dev_pm_qos_request_active(&info->dev_max_freq_req)) return 0; 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 (!dev_pm_qos_request_active(&info->dev_max_freq_req)) return 0; 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; struct arm_smccc_res res; 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); if (devp->opp_info && devp->opp_info->pvtpll_low_temp) { res = sip_smc_pvtpll_config(PVTPLL_LOW_TEMP, devp->opp_info->pvtpll_clk_id, is_low, 0, 0, 0, 0); if (res.a0) dev_err(info->dev, "%s: error cfg id=%u low temp %d (%d)\n", __func__, devp->opp_info->pvtpll_clk_id, is_low, (int)res.a0); } } 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_TYPE_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; if (!system_monitor->tz) 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->devp->data) return 0; 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_TYPE_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_TYPE_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_TYPE_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 (old_volt > new_volt) { 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; } } 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; } } if (old_volt <= new_volt) { 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 (old_volt <= new_volt) regulator_set_voltage(vdd_reg, old_volt, INT_MAX); if (info->regulator_count > 1) regulator_set_voltage(mem_reg, old_mem_volt, INT_MAX); if (old_volt > new_volt) 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); if (!devp) return ERR_PTR(-EINVAL); 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_TYPE_CPU) { if (freq_qos_request_active(&info->max_temp_freq_req)) freq_qos_remove_request(&info->max_temp_freq_req); if (freq_qos_request_active(&info->min_sta_freq_req)) freq_qos_remove_request(&info->min_sta_freq_req); if (freq_qos_request_active(&info->max_sta_freq_req)) freq_qos_remove_request(&info->max_sta_freq_req); } else { if (dev_pm_qos_request_active(&info->dev_max_freq_req)) 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 (!freq_qos_request_active(&info->min_sta_freq_req)) return; if (!freq_qos_request_active(&info->max_sta_freq_req)) return; 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_TYPE_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_TYPE_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 "); MODULE_DESCRIPTION("rockchip system monitor driver");