/* * drivers/misc/therm_est.c * * Copyright (c) 2010-2014, 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 struct therm_estimator { struct thermal_zone_device *thz; int num_trips; struct thermal_trip_info *trips; struct thermal_zone_params *tzp; int num_timer_trips; struct therm_est_timer_trip_info *timer_trips; struct delayed_work timer_trip_work; struct mutex timer_trip_lock; 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; int ntemp; long toffset; long polling_period; int polling_enabled; int tc1; int tc2; int ndevs; struct therm_est_subdevice *devs; int use_activator; #ifdef CONFIG_PM struct notifier_block pm_nb; #endif }; #define TIMER_TRIP_INACTIVE -2 #define TIMER_TRIP_STATE_NONE 0 #define TIMER_TRIP_STATE_START BIT(0) #define TIMER_TRIP_STATE_STOP BIT(1) #define TIMER_TRIP_STATE_UP BIT(2) #define TIMER_TRIP_STATE_DOWN BIT(3) static int __get_trip_temp(struct thermal_zone_device *thz, int trip, long *temp); static struct therm_est_timer_trip_info * __find_timer_trip(struct therm_estimator *est, int trip) { int i; /* Find matched timer trip info with trip. */ for (i = 0; i < est->num_timer_trips; i++) { if (est->timer_trips[i].trip == trip) return &est->timer_trips[i]; } return NULL; } static int __get_timer_trip_delay(struct therm_est_timer_trip_info *timer_info, s64 now, s64 *delay) { int cur = timer_info->cur; int next = (cur + 1 < timer_info->num_timers) ? cur + 1 : cur; if (cur == next) /* No more timer on this trip. */ return -ENOENT; *delay = timer_info->timers[next].time_after - (now - timer_info->last_tripped); return 0; } 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, long *temp) { if (!thz || 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; long trip_temp, passive_low_temp = MAX_HIGH_TEMP; enum thermal_trip_type trip_type; struct thermal_trip_info *trip_state; int i; for (i = 0; i < est->num_trips; i++) { trip_state = &est->trips[i]; __get_trip_temp(est->thz, i, &trip_temp); est->thz->ops->get_trip_type(est->thz, i, &trip_type); if (!trip_state->tripped) { /* 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(low_temp, passive_low_temp); est->low_limit = low_temp; est->high_limit = high_temp; } static void therm_est_update_timer_trips(struct therm_estimator *est) { struct thermal_trip_info *trip_state; struct therm_est_timer_trip_info *timer_info; s64 now, delay, min_delay; int i; mutex_lock(&est->timer_trip_lock); min_delay = LLONG_MAX; now = ktime_to_ms(ktime_get()); for (i = 0; i < est->num_timer_trips; i++) { timer_info = &est->timer_trips[i]; trip_state = &est->trips[timer_info->trip]; pr_debug("%s: i %d, trip %d, tripped %d, cur %d\n", __func__, i, timer_info->trip, trip_state->tripped, timer_info->cur); if ((timer_info->cur == TIMER_TRIP_INACTIVE) || (__get_timer_trip_delay(timer_info, now, &delay) < 0)) continue; if (delay > 0) min_delay = min(min_delay, delay); pr_debug("%s: delay %lld, min_delay %lld\n", __func__, delay, min_delay); } mutex_unlock(&est->timer_trip_lock); cancel_delayed_work(&est->timer_trip_work); if (min_delay != LLONG_MAX) queue_delayed_work(est->workqueue, &est->timer_trip_work, msecs_to_jiffies(min_delay)); } static void therm_est_timer_trip_work_func(struct work_struct *work) { struct therm_estimator *est = container_of(work, struct therm_estimator, timer_trip_work.work); struct thermal_trip_info *trip_state; struct therm_est_timer_trip_info *timer_info; s64 now, delay; int timer_trip_state, i; mutex_lock(&est->timer_trip_lock); timer_trip_state = TIMER_TRIP_STATE_NONE; now = ktime_to_ms(ktime_get()); for (i = 0; i < est->num_timer_trips; i++) { timer_info = &est->timer_trips[i]; trip_state = &est->trips[timer_info->trip]; pr_debug("%s: i %d, trip %d, tripped %d, cur %d\n", __func__, i, timer_info->trip, trip_state->tripped, timer_info->cur); if ((timer_info->cur == TIMER_TRIP_INACTIVE) || (__get_timer_trip_delay(timer_info, now, &delay) < 0)) continue; if (delay <= 0) { /* Timer on this trip has expired. */ if (timer_info->cur + 1 < timer_info->num_timers) { timer_info->last_tripped = now; timer_info->cur++; timer_trip_state |= TIMER_TRIP_STATE_UP; } } /* If delay > 0, timer on this trip has not yet expired. * So need to restart timer with remaining delay. */ timer_trip_state |= TIMER_TRIP_STATE_START; pr_debug("%s: new_cur %d, delay %lld, timer_trip_state 0x%x\n", __func__, timer_info->cur, delay, timer_trip_state); } mutex_unlock(&est->timer_trip_lock); if (timer_trip_state & (TIMER_TRIP_STATE_START | TIMER_TRIP_STATE_UP)) { therm_est_update_timer_trips(est); therm_est_update_limits(est); } } static void therm_est_work_func(struct work_struct *work) { int i, j, index, sum = 0; long temp; struct delayed_work *dwork = container_of(work, struct delayed_work, work); struct therm_estimator *est = container_of(dwork, struct therm_estimator, therm_est_work); for (i = 0; i < est->ndevs; i++) { if (therm_est_subdev_get_temp(est->devs[i].sub_thz, &temp)) continue; est->devs[i].hist[(est->ntemp % HIST_LEN)] = temp; } for (i = 0; i < est->ndevs; i++) { for (j = 0; j < HIST_LEN; j++) { index = (est->ntemp - j + HIST_LEN) % HIST_LEN; sum += est->devs[i].hist[index] * est->devs[i].coeffs[j]; } } est->cur_temp = sum / 100 + est->toffset; est->ntemp++; if (est->thz && ((est->cur_temp < est->low_limit) || (est->cur_temp >= est->high_limit))) { thermal_zone_device_update(est->thz); therm_est_update_timer_trips(est); 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_bind(struct thermal_zone_device *thz, struct thermal_cooling_device *cdev) { struct therm_estimator *est = thz->devdata; struct thermal_trip_info *trip_state; int i; for (i = 0; i < est->num_trips; i++) { trip_state = &est->trips[i]; if (trip_state->cdev_type && !strncmp(trip_state->cdev_type, cdev->type, THERMAL_NAME_LENGTH)) thermal_zone_bind_cooling_device(thz, i, cdev, trip_state->upper, trip_state->lower); } return 0; } static int therm_est_unbind(struct thermal_zone_device *thz, struct thermal_cooling_device *cdev) { struct therm_estimator *est = thz->devdata; struct thermal_trip_info *trip_state; int i; for (i = 0; i < est->num_trips; i++) { trip_state = &est->trips[i]; if (trip_state->cdev_type && !strncmp(trip_state->cdev_type, cdev->type, THERMAL_NAME_LENGTH)) thermal_zone_unbind_cooling_device(thz, i, cdev); } return 0; } static int therm_est_get_trip_type(struct thermal_zone_device *thz, int trip, enum thermal_trip_type *type) { struct therm_estimator *est = thz->devdata; *type = est->trips[trip].trip_type; return 0; } static int __get_trip_temp(struct thermal_zone_device *thz, int trip, long *temp) { struct therm_estimator *est = thz->devdata; struct thermal_trip_info *trip_state = &est->trips[trip]; struct therm_est_timer_trip_info *timer_info; long zone_temp, trip_temp, hysteresis; int cur = TIMER_TRIP_INACTIVE; int ret = TIMER_TRIP_STATE_NONE; zone_temp = thz->temperature; trip_temp = trip_state->trip_temp; hysteresis = trip_state->hysteresis; timer_info = __find_timer_trip(est, trip); if (timer_info) { cur = timer_info->cur; /* If timer trip is available, use trip_temp and hysteresis in * the timer trip to trip_temp for this trip. */ if (timer_info->cur >= 0) { trip_temp = timer_info->timers[cur].trip_temp; hysteresis = timer_info->timers[cur].hysteresis; } } if (zone_temp >= trip_temp) { trip_temp -= hysteresis; if (timer_info && !trip_state->tripped) ret = TIMER_TRIP_STATE_START; trip_state->tripped = true; } else if (trip_state->tripped) { trip_temp -= hysteresis; if (zone_temp < trip_temp) { if (!timer_info) { trip_state->tripped = false; } else { if (cur == TIMER_TRIP_INACTIVE) trip_state->tripped = false; else ret = TIMER_TRIP_STATE_DOWN; } } } *temp = trip_temp; return ret; } static int therm_est_get_trip_temp(struct thermal_zone_device *thz, int trip, long *temp) { struct therm_estimator *est = thz->devdata; struct therm_est_timer_trip_info *timer_info; int ret; ret = __get_trip_temp(thz, trip, temp); if (ret & (TIMER_TRIP_STATE_START | TIMER_TRIP_STATE_DOWN)) { timer_info = __find_timer_trip(est, trip); mutex_lock(&est->timer_trip_lock); timer_info->last_tripped = ktime_to_ms(ktime_get()); if (ret & TIMER_TRIP_STATE_START) { timer_info->cur = TIMER_TRIP_INACTIVE + 1; } else if (ret & TIMER_TRIP_STATE_DOWN) { if (--timer_info->cur < TIMER_TRIP_INACTIVE) timer_info->cur = TIMER_TRIP_INACTIVE; } mutex_unlock(&est->timer_trip_lock); /* Update limits, because trip temp was changed by timer trip * changing. */ therm_est_update_limits(est); } return 0; } static int therm_est_set_trip_temp(struct thermal_zone_device *thz, int trip, long temp) { struct therm_estimator *est = thz->devdata; est->trips[trip].trip_temp = temp; /* Update limits, because trip temp was changed. */ therm_est_update_limits(est); return 0; } static int therm_est_get_temp(struct thermal_zone_device *thz, long *temp) { struct therm_estimator *est = thz->devdata; *temp = est->cur_temp; return 0; } static int therm_est_get_trend(struct thermal_zone_device *thz, int trip, enum thermal_trend *trend) { struct therm_estimator *est = thz->devdata; struct thermal_trip_info *trip_state = &est->trips[trip]; long trip_temp; int new_trend; int cur_temp; __get_trip_temp(thz, trip, &trip_temp); cur_temp = thz->temperature; new_trend = (est->tc1 * (cur_temp - thz->last_temperature)) + (est->tc2 * (cur_temp - trip_temp)); switch (trip_state->trip_type) { case THERMAL_TRIP_ACTIVE: /* aggressive active cooling */ *trend = THERMAL_TREND_RAISING; break; case THERMAL_TRIP_PASSIVE: if (new_trend > 0) *trend = THERMAL_TREND_RAISING; else if (new_trend < 0) *trend = THERMAL_TREND_DROPPING; else *trend = THERMAL_TREND_STABLE; break; default: return -EINVAL; } return 0; } static void therm_est_init_timer_trips(struct therm_estimator *est) { int i; for (i = 0; i < est->num_timer_trips; i++) est->timer_trips[i].cur = TIMER_TRIP_INACTIVE; } static int therm_est_init_history(struct therm_estimator *est) { int i, j; struct therm_est_subdevice *dev; long temp; for (i = 0; i < est->ndevs; i++) { dev = &est->devs[i]; if (therm_est_subdev_get_temp(dev->sub_thz, &temp)) return -EINVAL; for (j = 0; j < HIST_LEN; j++) dev->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); therm_est_init_timer_trips(est); queue_delayed_work(est->workqueue, &est->therm_est_work, msecs_to_jiffies(est->polling_period)); } else { est->cur_temp = 25000; cancel_delayed_work_sync(&est->therm_est_work); } return 0; } static struct thermal_zone_device_ops therm_est_ops = { .bind = therm_est_bind, .unbind = therm_est_unbind, .get_trip_type = therm_est_get_trip_type, .get_trip_temp = therm_est_get_trip_temp, .set_trip_temp = therm_est_set_trip_temp, .get_temp = therm_est_get_temp, .get_trend = therm_est_get_trend, }; static ssize_t show_coeff(struct device *dev, struct device_attribute *da, char *buf) { struct therm_estimator *est = dev_get_drvdata(dev); ssize_t len, total_len = 0; int i, j; for (i = 0; i < est->ndevs; i++) { len = snprintf(buf + total_len, PAGE_SIZE - total_len, "[%d]", i); total_len += len; for (j = 0; j < HIST_LEN; j++) { len = snprintf(buf + total_len, PAGE_SIZE - total_len, " %ld", est->devs[i].coeffs[j]); total_len += len; } len = snprintf(buf + total_len, PAGE_SIZE - total_len, "\n"); total_len += len; } 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 devid, scount; long coeff[20]; if (HIST_LEN > 20) return -EINVAL; scount = sscanf(buf, "[%d] %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld " \ "%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld", &devid, &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 + 1) return -1; if (devid < 0 || devid >= est->ndevs) return -EINVAL; /* This has obvious locking issues but don't worry about it */ memcpy(est->devs[devid].coeffs, coeff, sizeof(coeff[0]) * HIST_LEN); return count; } static ssize_t show_offset(struct device *dev, struct device_attribute *da, char *buf) { struct therm_estimator *est = dev_get_drvdata(dev); snprintf(buf, PAGE_SIZE, "%ld\n", est->toffset); return strlen(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; if (kstrtoint(buf, 0, &offset)) return -EINVAL; est->toffset = offset; return count; } static ssize_t show_temps(struct device *dev, struct device_attribute *da, char *buf) { struct therm_estimator *est = dev_get_drvdata(dev); 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 < est->ndevs; i++) { total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, "[%d]", i); for (j = 0; j < HIST_LEN; j++) { index = (est->ntemp - j + HIST_LEN) % HIST_LEN; total_len += snprintf(buf + total_len, PAGE_SIZE - total_len, " %ld", est->devs[i].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(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); cancel_delayed_work_sync(&est->timer_trip_work); break; case PM_POST_SUSPEND: est->low_limit = 0; est->high_limit = 0; therm_est_init_history(est); therm_est_init_timer_trips(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_probe(struct platform_device *pdev) { int i; struct therm_estimator *est; struct therm_est_data *data; struct thermal_zone_device *thz; 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; for (i = 0; i < data->ndevs; i++) { thz = thermal_zone_device_find(data->devs[i].dev_data, therm_est_subdev_match); if (!thz) goto err; data->devs[i].sub_thz = thz; } est->devs = data->devs; est->ndevs = data->ndevs; est->toffset = data->toffset; est->polling_period = data->polling_period; est->polling_enabled = 0; /* By default polling is switched off */ est->tc1 = data->tc1; est->tc2 = data->tc2; est->use_activator = data->use_activator; /* initialize history */ therm_est_init_history(est); /* initialize timer trips */ est->num_timer_trips = data->num_timer_trips; est->timer_trips = data->timer_trips; therm_est_init_timer_trips(est); mutex_init(&est->timer_trip_lock); INIT_DELAYED_WORK(&est->timer_trip_work, therm_est_timer_trip_work_func); 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->num_trips = data->num_trips; est->trips = data->trips; est->tzp = data->tzp; est->thz = thermal_zone_device_register(dev_name(&pdev->dev), est->num_trips, (1ULL << est->num_trips) - 1, est, &therm_est_ops, est->tzp, data->passive_delay, 0); 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); cancel_delayed_work_sync(&est->timer_trip_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_zone_device_unregister(est->thz); 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); cancel_delayed_work_sync(&est->timer_trip_work); thermal_zone_device_unregister(est->thz); thermal_cooling_device_unregister(est->cdev); } static struct platform_driver therm_est_driver = { .driver = { .owner = THIS_MODULE, .name = "therm_est", }, .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); } module_init(therm_est_driver_init);