| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * step_wise.c - A step-by-step Thermal throttling governor |
| * |
| * Copyright (C) 2012 Intel Corp |
| * Copyright (C) 2012 Durgadoss R <durgadoss.r@intel.com> |
| * |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| * |
| * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| */ |
| |
| #include <linux/thermal.h> |
| #include <linux/minmax.h> |
| #include "thermal_trace.h" |
| |
| #include "thermal_core.h" |
| |
| /* |
| * If the temperature is higher than a trip point, |
| * a. if the trend is THERMAL_TREND_RAISING, use higher cooling |
| * state for this trip point |
| * b. if the trend is THERMAL_TREND_DROPPING, do nothing |
| * If the temperature is lower than a trip point, |
| * a. if the trend is THERMAL_TREND_RAISING, do nothing |
| * b. if the trend is THERMAL_TREND_DROPPING, use lower cooling |
| * state for this trip point, if the cooling state already |
| * equals lower limit, deactivate the thermal instance |
| */ |
| static unsigned long get_target_state(struct thermal_instance *instance, |
| enum thermal_trend trend, bool throttle) |
| { |
| struct thermal_cooling_device *cdev = instance->cdev; |
| unsigned long cur_state; |
| unsigned long next_target; |
| |
| /* |
| * We keep this instance the way it is by default. |
| * Otherwise, we use the current state of the |
| * cdev in use to determine the next_target. |
| */ |
| cdev->ops->get_cur_state(cdev, &cur_state); |
| next_target = instance->target; |
| dev_dbg(&cdev->device, "cur_state=%ld\n", cur_state); |
| |
| if (!instance->initialized) { |
| if (throttle) { |
| next_target = clamp((cur_state + 1), instance->lower, instance->upper); |
| } else { |
| next_target = THERMAL_NO_TARGET; |
| } |
| |
| return next_target; |
| } |
| |
| if (throttle) { |
| if (trend == THERMAL_TREND_RAISING) |
| next_target = clamp((cur_state + 1), instance->lower, instance->upper); |
| } else { |
| if (trend == THERMAL_TREND_DROPPING) { |
| if (cur_state <= instance->lower) |
| next_target = THERMAL_NO_TARGET; |
| else |
| next_target = clamp((cur_state - 1), instance->lower, instance->upper); |
| } |
| } |
| |
| return next_target; |
| } |
| |
| static void update_passive_instance(struct thermal_zone_device *tz, |
| enum thermal_trip_type type, int value) |
| { |
| /* |
| * If value is +1, activate a passive instance. |
| * If value is -1, deactivate a passive instance. |
| */ |
| if (type == THERMAL_TRIP_PASSIVE) |
| tz->passive += value; |
| } |
| |
| static void thermal_zone_trip_update(struct thermal_zone_device *tz, int trip_id) |
| { |
| enum thermal_trend trend; |
| struct thermal_instance *instance; |
| struct thermal_trip trip; |
| bool throttle = false; |
| int old_target; |
| |
| __thermal_zone_get_trip(tz, trip_id, &trip); |
| |
| trend = get_tz_trend(tz, trip_id); |
| |
| if (tz->temperature >= trip.temperature) { |
| throttle = true; |
| trace_thermal_zone_trip(tz, trip_id, trip.type); |
| } |
| |
| dev_dbg(&tz->device, "Trip%d[type=%d,temp=%d]:trend=%d,throttle=%d\n", |
| trip_id, trip.type, trip.temperature, trend, throttle); |
| |
| list_for_each_entry(instance, &tz->thermal_instances, tz_node) { |
| if (instance->trip != trip_id) |
| continue; |
| |
| old_target = instance->target; |
| instance->target = get_target_state(instance, trend, throttle); |
| dev_dbg(&instance->cdev->device, "old_target=%d, target=%d\n", |
| old_target, (int)instance->target); |
| |
| if (instance->initialized && old_target == instance->target) |
| continue; |
| |
| /* Activate a passive thermal instance */ |
| if (old_target == THERMAL_NO_TARGET && |
| instance->target != THERMAL_NO_TARGET) |
| update_passive_instance(tz, trip.type, 1); |
| /* Deactivate a passive thermal instance */ |
| else if (old_target != THERMAL_NO_TARGET && |
| instance->target == THERMAL_NO_TARGET) |
| update_passive_instance(tz, trip.type, -1); |
| |
| instance->initialized = true; |
| mutex_lock(&instance->cdev->lock); |
| instance->cdev->updated = false; /* cdev needs update */ |
| mutex_unlock(&instance->cdev->lock); |
| } |
| } |
| |
| /** |
| * step_wise_throttle - throttles devices associated with the given zone |
| * @tz: thermal_zone_device |
| * @trip: trip point index |
| * |
| * Throttling Logic: This uses the trend of the thermal zone to throttle. |
| * If the thermal zone is 'heating up' this throttles all the cooling |
| * devices associated with the zone and its particular trip point, by one |
| * step. If the zone is 'cooling down' it brings back the performance of |
| * the devices by one step. |
| */ |
| static int step_wise_throttle(struct thermal_zone_device *tz, int trip) |
| { |
| struct thermal_instance *instance; |
| |
| lockdep_assert_held(&tz->lock); |
| |
| thermal_zone_trip_update(tz, trip); |
| |
| list_for_each_entry(instance, &tz->thermal_instances, tz_node) |
| thermal_cdev_update(instance->cdev); |
| |
| return 0; |
| } |
| |
| static struct thermal_governor thermal_gov_step_wise = { |
| .name = "step_wise", |
| .throttle = step_wise_throttle, |
| }; |
| THERMAL_GOVERNOR_DECLARE(thermal_gov_step_wise); |