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