/* * drivers/misc/therm_est.c * * Copyright (c) 2010-2017, NVIDIA CORPORATION. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_TSKIN 25000 /* default tskin in mC */ struct therm_estimator { struct thermal_zone_device *thz; struct thermal_cooling_device *cdev; /* activation device */ struct workqueue_struct *workqueue; struct delayed_work therm_est_work; long cur_temp; long low_limit; long high_limit; long polling_period; int polling_enabled; int tc1; int tc2; struct therm_est_subdevice *subdevice; bool *tripped_info; int use_activator; #ifdef CONFIG_PM struct notifier_block pm_nb; #endif }; static int therm_est_subdev_match(struct thermal_zone_device *thz, void *data) { return strcmp((char *)data, thz->type) == 0; } static int therm_est_subdev_get_temp(struct thermal_zone_device *thz, int *temp) { if (!thz || !thz->ops->get_temp || thz->ops->get_temp(thz, temp)) *temp = 25000; return 0; } static void therm_est_update_limits(struct therm_estimator *est) { const int MAX_HIGH_TEMP = 128000; long low_temp = 0, high_temp = MAX_HIGH_TEMP; int trip_temp, passive_low_temp = MAX_HIGH_TEMP; enum thermal_trip_type trip_type; int hysteresis, zone_temp; int i; zone_temp = est->thz->temperature; for (i = 0; i < est->thz->trips; i++) { est->thz->ops->get_trip_temp(est->thz, i, &trip_temp); est->thz->ops->get_trip_hyst(est->thz, i, &hysteresis); est->thz->ops->get_trip_type(est->thz, i, &trip_type); if (zone_temp >= trip_temp) { trip_temp -= hysteresis; est->tripped_info[i] = true; } else if (est->tripped_info[i]) { trip_temp -= hysteresis; if (zone_temp < trip_temp) est->tripped_info[i] = false; } if (!est->tripped_info[i]) { /* not tripped? update high */ if (trip_temp < high_temp) high_temp = trip_temp; } else { /* tripped? update low */ if (trip_type != THERMAL_TRIP_PASSIVE) { /* get highest ACTIVE */ if (trip_temp > low_temp) low_temp = trip_temp; } else { /* get lowest PASSIVE */ if (trip_temp < passive_low_temp) passive_low_temp = trip_temp; } } } if (passive_low_temp != MAX_HIGH_TEMP) low_temp = max_t(long, low_temp, passive_low_temp); est->low_limit = low_temp; est->high_limit = high_temp; } static void therm_est_work_func(struct work_struct *work) { struct delayed_work *dwork = container_of(work, struct delayed_work, work); struct therm_estimator *est = container_of(dwork, struct therm_estimator, therm_est_work); struct therm_est_subdevice *subdevice; struct therm_est_coeffs *coeffs_set; s32 *thz_coeffs; long *hist; int temp; int i, j, index, sum = 0; subdevice = est->subdevice; coeffs_set = &subdevice->coeffs_set[subdevice->active_coeffs]; subdevice->ntemp = (subdevice->ntemp + 1) % HIST_LEN; for (i = 0; i < subdevice->num_devs; i++) { if (therm_est_subdev_get_temp(subdevice->sub_thz[i].thz, &temp)) continue; subdevice->sub_thz[i].hist[subdevice->ntemp] = temp; } for (i = 0; i < subdevice->num_devs; i++) { hist = subdevice->sub_thz[i].hist; thz_coeffs = coeffs_set->coeffs[i]; for (j = 0; j < HIST_LEN; j++) { index = (subdevice->ntemp - j + HIST_LEN) % HIST_LEN; sum += hist[index] * thz_coeffs[j]; } } est->cur_temp = sum / 100 + coeffs_set->toffset; if (est->thz && ((est->cur_temp < est->low_limit) || (est->cur_temp >= est->high_limit))) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) thermal_zone_device_update(est->thz, THERMAL_EVENT_UNSPECIFIED); #else thermal_zone_device_update(est->thz); #endif therm_est_update_limits(est); } if (est->polling_enabled > 0 || !est->use_activator) { queue_delayed_work(est->workqueue, &est->therm_est_work, msecs_to_jiffies(est->polling_period)); } } static int therm_est_get_temp(void *of_data, int *temp) { struct therm_estimator *est = (struct therm_estimator *)of_data; *temp = est->cur_temp; return 0; } static int therm_est_get_trend(void *of_data, int trip, enum thermal_trend *trend) { struct therm_estimator *est = (struct therm_estimator *)of_data; if (IS_ERR_OR_NULL(est->thz)) *trend = THERMAL_TREND_STABLE; else if (est->thz->temperature > est->thz->last_temperature + 100) *trend = THERMAL_TREND_RAISING; else if (est->thz->temperature < est->thz->last_temperature - 100) *trend = THERMAL_TREND_DROPPING; else *trend = THERMAL_TREND_STABLE; return 0; } static int therm_est_trip_update(void *of_data, int trip) { struct therm_estimator *est = (struct therm_estimator *)of_data; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) thermal_zone_device_update(est->thz, THERMAL_EVENT_UNSPECIFIED); #else thermal_zone_device_update(est->thz); #endif therm_est_update_limits(est); return 0; } static int therm_est_init_history(struct therm_estimator *est) { struct therm_est_sub_thz *sub_thz; int temp; int i, j; sub_thz = est->subdevice->sub_thz; for (i = 0; i < est->subdevice->num_devs; i++) { if (therm_est_subdev_get_temp(sub_thz[i].thz, &temp)) return -EINVAL; for (j = 0; j < HIST_LEN; j++) sub_thz[i].hist[j] = temp; } return 0; } static int therm_est_polling(struct therm_estimator *est, int polling) { est->polling_enabled = polling > 0; if (est->polling_enabled > 0) { est->low_limit = 0; est->high_limit = 0; therm_est_init_history(est); queue_delayed_work(est->workqueue, &est->therm_est_work, msecs_to_jiffies(est->polling_period)); } else { cancel_delayed_work_sync(&est->therm_est_work); est->cur_temp = DEFAULT_TSKIN; } return 0; } static int switch_active_coeffs(struct therm_estimator *est, int active_coeffs) { struct therm_est_subdevice *subdevice = est->subdevice; if (active_coeffs < 0 || active_coeffs >= subdevice->num_coeffs) return -EINVAL; subdevice->active_coeffs = active_coeffs; return 0; } static ssize_t show_coeff(struct device *dev, struct device_attribute *da, char *buf) { struct therm_estimator *est = dev_get_drvdata(dev); struct therm_est_subdevice *subdevice = est->subdevice; struct therm_est_coeffs *coeffs_set; s32 *coeffs; ssize_t total_len = 0; int i, j, k; total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "Total %02d set(s) are available\n\n", subdevice->num_coeffs); for (i = 0; i < subdevice->num_coeffs; i++) { total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "- SET %02d - %s\n", i, (i == subdevice->active_coeffs)?"active":""); coeffs_set = subdevice->coeffs_set + i; for (j = 0; j < subdevice->num_devs; j++) { total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "%s : ", subdevice->sub_thz[j].thz->type); coeffs = coeffs_set->coeffs[j]; for (k = 0; k < HIST_LEN; k++) { total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, " %d", coeffs[k]); } total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "\n"); } total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "toffset : %ld\n\n", coeffs_set->toffset); } return strlen(buf); } static ssize_t set_coeff(struct device *dev, struct device_attribute *da, const char *buf, size_t count) { struct therm_estimator *est = dev_get_drvdata(dev); int coeffs_index, dev_index, scount; s32 coeff[20]; if (HIST_LEN > 20) return -EINVAL; scount = sscanf(buf, "[%d][%d] %d %d %d %d %d %d %d %d %d " \ "%d %d %d %d %d %d %d %d %d %d %d", &coeffs_index, &dev_index, &coeff[0], &coeff[1], &coeff[2], &coeff[3], &coeff[4], &coeff[5], &coeff[6], &coeff[7], &coeff[8], &coeff[9], &coeff[10], &coeff[11], &coeff[12], &coeff[13], &coeff[14], &coeff[15], &coeff[16], &coeff[17], &coeff[18], &coeff[19]); if (scount != HIST_LEN + 2) return -1; if (dev_index < 0 || dev_index >= est->subdevice->num_devs) return -EINVAL; if (coeffs_index < 0 || coeffs_index >= est->subdevice->num_coeffs) return -EINVAL; /* This has obvious locking issues but don't worry about it */ memcpy(est->subdevice->coeffs_set[coeffs_index].coeffs[dev_index], coeff, sizeof(coeff[0]) * HIST_LEN); return count; } static ssize_t show_offset(struct device *dev, struct device_attribute *da, char *buf) { return show_coeff(dev, da, buf); } static ssize_t set_offset(struct device *dev, struct device_attribute *da, const char *buf, size_t count) { struct therm_estimator *est = dev_get_drvdata(dev); int offset, scount, coeffs_index; scount = sscanf(buf, "[%d] %d", &coeffs_index, &offset); if (scount != 2) return -EINVAL; if (coeffs_index < 0 || coeffs_index >= est->subdevice->num_coeffs) return -EINVAL; est->subdevice->coeffs_set[coeffs_index].toffset = offset; return count; } static ssize_t show_active_coeffs(struct device *dev, struct device_attribute *da, char *buf) { struct therm_estimator *est = dev_get_drvdata(dev); struct therm_est_subdevice *subdevice = est->subdevice; struct therm_est_coeffs *coeffs_set; s32 *coeffs; ssize_t total_len = 0; int i, j; total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "There are %d set(s) and the active set is [%02d]\n\n", subdevice->num_coeffs, subdevice->active_coeffs); coeffs_set = subdevice->coeffs_set + subdevice->active_coeffs; for (i = 0; i < subdevice->num_devs; i++) { total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "%s : ", subdevice->sub_thz[i].thz->type); coeffs = coeffs_set->coeffs[i]; for (j = 0; j < HIST_LEN; j++) { total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, " %d", coeffs[j]); } total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "\n"); } total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "toffset : %ld\n", coeffs_set->toffset); return strlen(buf); } static ssize_t set_active_coeffs(struct device *dev, struct device_attribute *da, const char *buf, size_t count) { struct therm_estimator *est = dev_get_drvdata(dev); int active_coeffs, ret; if (kstrtoint(buf, 0, &active_coeffs)) return -EINVAL; ret = switch_active_coeffs(est, active_coeffs); if (ret) return ret; return count; } static ssize_t show_temps(struct device *dev, struct device_attribute *da, char *buf) { struct therm_estimator *est = dev_get_drvdata(dev); struct therm_est_subdevice *subdevice = est->subdevice; long *hist; ssize_t total_len = 0; int i, j; int index; /* This has obvious locking issues but don't worry about it */ for (i = 0; i < subdevice->num_devs; i++) { total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "[%d]", i); hist = subdevice->sub_thz[i].hist; for (j = 0; j < HIST_LEN; j++) { index = (subdevice->ntemp - j + HIST_LEN) % HIST_LEN; total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, " %ld", hist[index]); } total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "\n"); } return strlen(buf); } static ssize_t show_tc1(struct device *dev, struct device_attribute *da, char *buf) { struct therm_estimator *est = dev_get_drvdata(dev); snprintf(buf, PAGE_SIZE, "%d\n", est->tc1); return strlen(buf); } static ssize_t set_tc1(struct device *dev, struct device_attribute *da, const char *buf, size_t count) { struct therm_estimator *est = dev_get_drvdata(dev); int tc1; if (kstrtoint(buf, 0, &tc1)) return -EINVAL; est->tc1 = tc1; return count; } static ssize_t show_tc2(struct device *dev, struct device_attribute *da, char *buf) { struct therm_estimator *est = dev_get_drvdata(dev); snprintf(buf, PAGE_SIZE, "%d\n", est->tc2); return strlen(buf); } static ssize_t set_tc2(struct device *dev, struct device_attribute *da, const char *buf, size_t count) { struct therm_estimator *est = dev_get_drvdata(dev); int tc2; if (kstrtoint(buf, 0, &tc2)) return -EINVAL; est->tc2 = tc2; return count; } static struct sensor_device_attribute therm_est_nodes[] = { SENSOR_ATTR(coeff, S_IRUGO | S_IWUSR, show_coeff, set_coeff, 0), SENSOR_ATTR(offset, S_IRUGO | S_IWUSR, show_offset, set_offset, 0), SENSOR_ATTR(active_coeffs, S_IRUGO | S_IWUSR, show_active_coeffs, set_active_coeffs, 0), SENSOR_ATTR(tc1, S_IRUGO | S_IWUSR, show_tc1, set_tc1, 0), SENSOR_ATTR(tc2, S_IRUGO | S_IWUSR, show_tc2, set_tc2, 0), SENSOR_ATTR(temps, S_IRUGO, show_temps, 0, 0), }; #ifdef CONFIG_PM static int therm_est_pm_notify(struct notifier_block *nb, unsigned long event, void *data) { struct therm_estimator *est = container_of( nb, struct therm_estimator, pm_nb); switch (event) { case PM_SUSPEND_PREPARE: cancel_delayed_work_sync(&est->therm_est_work); break; case PM_POST_SUSPEND: est->low_limit = 0; est->high_limit = 0; therm_est_init_history(est); queue_delayed_work(est->workqueue, &est->therm_est_work, msecs_to_jiffies(est->polling_period)); break; } return NOTIFY_OK; } #endif static int thermal_est_activation_get_max_state(struct thermal_cooling_device *cdev, unsigned long *max_state) { *max_state = 1; return 0; } static int thermal_est_activation_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *cur_state) { struct therm_estimator *est = cdev->devdata; *cur_state = est->polling_enabled; return 0; } static int thermal_est_activation_set_cur_state(struct thermal_cooling_device *cdev, unsigned long cur_state) { struct therm_estimator *est = cdev->devdata; if (est->use_activator) therm_est_polling(est, cur_state > 0); return 0; } static struct thermal_cooling_device_ops thermal_est_activation_device_ops = { .get_max_state = thermal_est_activation_get_max_state, .get_cur_state = thermal_est_activation_get_cur_state, .set_cur_state = thermal_est_activation_set_cur_state, }; struct thermal_cooling_device *thermal_est_activation_device_register( struct therm_estimator *est, char *type) { struct thermal_cooling_device *cdev; cdev = thermal_cooling_device_register( type, est, &thermal_est_activation_device_ops); if (IS_ERR(cdev)) return NULL; pr_debug("Therm_est: Cooling-device REGISTERED\n"); return cdev; } static int therm_est_get_subdev(struct device *dev, struct device_node *subdev_np, struct therm_est_subdevice *subdevice) { struct thermal_zone_device *thz; struct device_node *coeffs_np; char *thz_name; int num_subdevs; int num_coeffs; int i, ret = 0; s32 val; num_subdevs = of_property_count_strings(subdev_np, "subdev_names"); if (num_subdevs == 0) return -ENOENT; num_coeffs = of_get_child_count(subdev_np); if (num_coeffs == 0) return -ENOENT; subdevice->sub_thz = devm_kzalloc(dev, sizeof(*subdevice->sub_thz) * num_subdevs, GFP_KERNEL); if (!subdevice->sub_thz) return -ENOMEM; subdevice->coeffs_set = devm_kzalloc(dev, sizeof(*subdevice->coeffs_set) * num_coeffs, GFP_KERNEL); if (!subdevice->coeffs_set) return -ENOMEM; for (i = 0; i < num_coeffs; i++) { subdevice->coeffs_set[i].coeffs = devm_kzalloc(dev, sizeof(*subdevice->coeffs_set->coeffs) * num_subdevs, GFP_KERNEL); if (!subdevice->coeffs_set[i].coeffs) return -ENOMEM; } for (i = 0; i < num_subdevs; i++) { ret = of_property_read_string_index(subdev_np, "subdev_names", i, (const char**)&thz_name); if (ret) return -EINVAL; thz = thermal_zone_device_find(thz_name, therm_est_subdev_match); if (!thz) return -EINVAL; subdevice->sub_thz[i].thz = thz; } i = 0; for_each_child_of_node(subdev_np, coeffs_np) { ret = of_property_read_s32(coeffs_np, "toffset", &val); if (ret) return ret; subdevice->coeffs_set[i].toffset = val; /* * casting from s32* to u32* is safe here */ ret = of_property_read_u32_array(coeffs_np, "coeffs", (u32 *)subdevice->coeffs_set[i].coeffs, num_subdevs * HIST_LEN); i++; } of_node_put(coeffs_np); subdevice->num_coeffs = num_coeffs; subdevice->num_devs = num_subdevs; return 0; } static struct therm_est_data *therm_est_get_pdata(struct device *dev) { struct device_node *np, *subdev_np; struct therm_est_data *data; u32 val; int ret; np = dev->of_node; if (!np) return ERR_PTR(-ENOENT); data = devm_kzalloc(dev, sizeof(struct therm_est_data), GFP_KERNEL); if (!data) return ERR_PTR(-ENOMEM); ret = of_property_read_u32(np, "polling-period", &val); if (ret < 0) return ERR_PTR(ret); data->polling_period = val; ret = of_property_read_u32(np, "tc1", &val); if (ret < 0) return ERR_PTR(ret); data->tc1 = val; ret = of_property_read_u32(np, "tc2", &val); if (ret < 0) return ERR_PTR(ret); data->tc2 = val; ret = of_property_read_u32(np, "use_activator", &val); if (ret < 0) return ERR_PTR(ret); data->use_activator = val; subdev_np = of_get_child_by_name(np, "subdev"); if (!subdev_np) return ERR_PTR(-ENOENT); ret = therm_est_get_subdev(dev, subdev_np, &data->subdevice); if (ret) return ERR_PTR(ret); of_node_put(subdev_np); return data; } static struct thermal_zone_of_device_ops sops = { .get_temp = therm_est_get_temp, .get_trend = therm_est_get_trend, .trip_update = therm_est_trip_update, }; static int therm_est_probe(struct platform_device *pdev) { int i, ret; struct therm_estimator *est; struct therm_est_data *data; est = kzalloc(sizeof(struct therm_estimator), GFP_KERNEL); if (IS_ERR_OR_NULL(est)) return -ENOMEM; platform_set_drvdata(pdev, est); data = pdev->dev.platform_data; if (!data) { data = therm_est_get_pdata(&pdev->dev); if (IS_ERR_OR_NULL(data)) goto err; } est->subdevice = &data->subdevice; est->polling_period = data->polling_period; est->tc1 = data->tc1; est->tc2 = data->tc2; est->cur_temp = DEFAULT_TSKIN; est->use_activator = data->use_activator; /* initialize history */ therm_est_init_history(est); est->workqueue = alloc_workqueue(dev_name(&pdev->dev), WQ_HIGHPRI | WQ_UNBOUND, 1); if (!est->workqueue) goto err; INIT_DELAYED_WORK(&est->therm_est_work, therm_est_work_func); est->cdev = thermal_est_activation_device_register(est, "therm_est_activ"); est->thz = thermal_zone_of_sensor_register(&pdev->dev, 0, est, &sops); if (IS_ERR(est->thz)) { ret = PTR_ERR(est->thz); dev_err(&pdev->dev, "Device can not register as thermal sensor: %d\n", ret); goto err; } est->tripped_info = devm_kzalloc(&pdev->dev, sizeof(bool) * est->thz->trips, GFP_KERNEL); if (IS_ERR_OR_NULL(est->thz)) goto err; for (i = 0; i < ARRAY_SIZE(therm_est_nodes); i++) device_create_file(&pdev->dev, &therm_est_nodes[i].dev_attr); #ifdef CONFIG_PM est->pm_nb.notifier_call = therm_est_pm_notify, register_pm_notifier(&est->pm_nb); #endif if (!est->use_activator) queue_delayed_work(est->workqueue, &est->therm_est_work, msecs_to_jiffies(est->polling_period)); return 0; err: if (est->workqueue) destroy_workqueue(est->workqueue); kfree(est); return -EINVAL; } static int therm_est_remove(struct platform_device *pdev) { struct therm_estimator *est = platform_get_drvdata(pdev); int i; cancel_delayed_work_sync(&est->therm_est_work); #ifdef CONFIG_PM unregister_pm_notifier(&est->pm_nb); #endif for (i = 0; i < ARRAY_SIZE(therm_est_nodes); i++) device_remove_file(&pdev->dev, &therm_est_nodes[i].dev_attr); thermal_cooling_device_unregister(est->cdev); kfree(est->thz); destroy_workqueue(est->workqueue); kfree(est); return 0; } static void therm_est_shutdown(struct platform_device *pdev) { struct therm_estimator *est = platform_get_drvdata(pdev); cancel_delayed_work_sync(&est->therm_est_work); thermal_cooling_device_unregister(est->cdev); } static struct of_device_id therm_est_of_match[] = { { .compatible = "nvidia,therm-est", }, { }, }; static struct platform_driver therm_est_driver = { .driver = { .owner = THIS_MODULE, .name = "therm_est", .of_match_table = of_match_ptr(therm_est_of_match), }, .probe = therm_est_probe, .remove = therm_est_remove, .shutdown = therm_est_shutdown, }; static int __init therm_est_driver_init(void) { return platform_driver_register(&therm_est_driver); } device_initcall_sync(therm_est_driver_init);