@@ -235,6 +235,18 @@ config THERMAL_AGGREGATION
found in the device tree's "thermal-sensors" list when it contains
more than one entry.
+config THERMAL_AGGREGATION_AUTO
+ bool "Automatic Thermal Aggregation support"
+ depends on THERMAL_AGGREGATION
+ help
+ Automatically apply thermal aggregation of multiple related thermal
+ zones into a single one. Here "related" means such zones must have
+ the same trip points and cooling devices bound to them. This is an
+ alternative to the device tree's "thermal-sensors" list for testing
+ purpose without actually modifying the DTB. It is highly recommended
+ that the device tree method be used in preference to this for actual
+ system deployment.
+
config THERMAL_EMULATION
bool "Thermal emulation mode support"
help
@@ -755,6 +755,16 @@ static inline void thermal_remove_tz_from_aggregator(struct thermal_zone_device
{}
#endif /* CONFIG_THERMAL_AGGREGATION */
+#ifdef CONFIG_THERMAL_AGGREGATION_AUTO
+static void thermal_check_zone_for_aggregation(struct thermal_zone_device *target_tz);
+static void thermal_check_cdev_for_aggregation(struct thermal_cooling_device *new_cdev);
+#else
+static inline void thermal_check_zone_for_aggregation(struct thermal_zone_device *target_tz)
+{}
+static inline void thermal_check_cdev_for_aggregation(struct thermal_cooling_device *new_cdev)
+{}
+#endif /* CONFIG_THERMAL_AGGREGATION_AUTO */
+
/**
* thermal_bind_cdev_to_trip - bind a cooling device to a thermal zone
* @tz: pointer to struct thermal_zone_device
@@ -1073,6 +1083,8 @@ __thermal_cooling_device_register(struct device_node *np,
mutex_unlock(&thermal_list_lock);
+ thermal_check_cdev_for_aggregation(cdev);
+
return cdev;
out_cooling_dev:
@@ -1515,6 +1527,8 @@ thermal_zone_device_register_with_trips(const char *type,
if (atomic_cmpxchg(&tz->need_update, 1, 0))
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
+ thermal_check_zone_for_aggregation(tz);
+
thermal_notify_tz_create(tz);
thermal_debug_tz_add(tz);
@@ -2068,6 +2082,219 @@ void thermal_zone_device_aggregate(struct thermal_zone_device *tz,
mutex_unlock(&thermal_list_lock);
}
+#ifdef CONFIG_THERMAL_AGGREGATION_AUTO
+
+static bool is_aggregator(struct thermal_zone_device *tz)
+{
+ return !strcmp(tz->type, "aggregator");
+}
+
+/**
+ * thermal_trip_related - determine if two trips are equivalent
+ *
+ * @tt1, @tt2: thermal trip specs to compare
+ *
+ * Determine if given trips may be candidates for aggregation.
+ *
+ * Return: true if related for aggregation, false otherwise
+ */
+static bool thermal_trip_related(struct thermal_trip *tt1,
+ struct thermal_trip *tt2)
+{
+ return tt1->temperature == tt2->temperature &&
+ tt1->hysteresis == tt2->hysteresis &&
+ tt1->type == tt2->type &&
+ tt1->flags == tt2->flags;
+}
+
+static struct thermal_cooling_device *
+trip_to_cdev(struct thermal_zone_device *tz, int trip_idx)
+{
+ struct thermal_instance *ti;
+
+ list_for_each_entry(ti, &tz->thermal_instances, tz_node)
+ if (trip_to_trip_desc(ti->trip) == &tz->trips[trip_idx])
+ return ti->cdev;
+
+ return NULL;
+}
+
+/**
+ * thermal_zone_related - determine if two tz's are candidates for aggregation
+ *
+ * @tz1, @tz2: thermal zones to compare
+ *
+ * Return: true if related for aggregation, false otherwise
+ */
+static bool thermal_zone_related(struct thermal_zone_device *tz1,
+ struct thermal_zone_device *tz2)
+{
+ /* a tz can't aggregate with itself */
+ if (tz1 == tz2)
+ return false;
+
+ /* no relation possible if ops.should_bind is unset */
+ if (!tz1->ops.should_bind || !tz2->ops.should_bind)
+ return false;
+
+ /* a tz always relates to its aggregator */
+ if (tz1->aggregator == tz2 || tz2->aggregator == tz1)
+ return true;
+
+ /* related tz's must have the same number of trip points */
+ if (tz1->num_trips != tz2->num_trips)
+ return false;
+
+ /* tz's with no cdev bindings are not (yet) considered */
+ if (list_empty(&tz1->thermal_instances) ||
+ list_empty(&tz2->thermal_instances))
+ return false;
+
+ for (int i = 0; i < tz1->num_trips; i++) {
+ /* all trips must be related */
+ if (!thermal_trip_related(&tz1->trips[i].trip, &tz2->trips[i].trip))
+ return false;
+ /* cdevs for given trips must be the same */
+ if (trip_to_cdev(tz1, i) != trip_to_cdev(tz2, i))
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * find_related_tz - look for a tz aggregation candidate
+ *
+ * @target_tz: tz to compare against
+ *
+ * Return: candidate tz for aggregation, or NULL if none
+ */
+static struct thermal_zone_device *
+find_related_tz(struct thermal_zone_device *target_tz)
+{
+ struct thermal_zone_device *tz;
+
+ list_for_each_entry(tz, &thermal_tz_list, node) {
+ if (is_aggregated(tz))
+ continue;
+ if (is_aggregator(tz))
+ continue;
+ if (!thermal_zone_related(tz, target_tz))
+ continue;
+ return tz;
+ }
+
+ return NULL;
+}
+
+/**
+ * thermal_check_zone_for_aggregation - consider tz for aggregation
+ *
+ * @target_tz: tz to compare against
+ *
+ * Adds the provided tz to a compatible aggregator. If none found, look for
+ * the possibility to create a new aggregator if another compatible tz exists.
+ * This is called, notably, when a new tz is registered and potentially bound
+ * to existing cdevs.
+ */
+static void thermal_check_zone_for_aggregation(struct thermal_zone_device *target_tz)
+{
+ struct thermal_zone_aggregator *aggr;
+ struct thermal_zone_device *aggr_tz, *tz;
+
+ if (is_aggregator(target_tz))
+ return;
+
+ mutex_lock(&thermal_list_lock);
+ if (!thermal_zone_is_present(target_tz)) {
+ mutex_unlock(&thermal_list_lock);
+ return;
+ }
+
+ /* see if existing aggregators can appropriate this zone */
+ list_for_each_entry(aggr, &thermal_aggregator_list, node) {
+ aggr_tz = aggr->tz;
+ if (!thermal_zone_related(aggr_tz, target_tz))
+ continue;
+ pr_debug("aggr %s(%d) and zone %s(%d) are related\n",
+ aggr_tz->type, aggr_tz->id, target_tz->type, target_tz->id);
+ add_tz_to_aggregator(aggr_tz, target_tz);
+ mutex_unlock(&thermal_list_lock);
+ return;
+ }
+
+ /* see if non-aggregated zones can be aggregated */
+ tz = find_related_tz(target_tz);
+ if (!tz) {
+ mutex_unlock(&thermal_list_lock);
+ return;
+ }
+
+ pr_debug("zones %s(%d) and %s(%d) are related\n",
+ tz->type, tz->id, target_tz->type, target_tz->id);
+
+ mutex_unlock(&thermal_list_lock);
+ aggr_tz = create_thermal_aggregator(target_tz, "aggregator");
+ if (IS_ERR(aggr_tz)) {
+ pr_err("unable to create thermal aggregator (%ld)\n",
+ PTR_ERR(aggr_tz));
+ return;
+ }
+
+ mutex_lock(&thermal_list_lock);
+
+ /* the lock was momentarily dropped so need to revalide everything */
+ if (thermal_zone_is_present(target_tz)) {
+ tz = find_related_tz(target_tz);
+ if (tz) {
+ add_tz_to_aggregator(aggr_tz, target_tz);
+ add_tz_to_aggregator(aggr_tz, tz);
+ mutex_unlock(&thermal_list_lock);
+ return;
+ }
+ }
+
+ /* our match disappeared in the mean time */
+ free_thermal_aggregator_unlock(aggr_tz);
+}
+
+/**
+ * thermal_check_cdev_for_aggregation - consider aggregation after new cdev registration
+ *
+ * @new_cdev: cdev for which new thermal bindings might create aggregation candidates
+ *
+ * Consider tz's having thermal instance bindings with this new cdev as
+ * candidates for aggregation. This is called when a new cdev is registered
+ * and potentially bound to existing tz's.
+ */
+static void thermal_check_cdev_for_aggregation(struct thermal_cooling_device *new_cdev)
+{
+ struct thermal_zone_device *tz, *last_tz = NULL;
+ struct thermal_instance *ti;
+
+start_over:
+ mutex_lock(&thermal_list_lock);
+
+ list_for_each_entry(tz, &thermal_tz_list, node) {
+ if (tz == last_tz)
+ continue;
+ if (is_aggregator(tz))
+ continue;
+ list_for_each_entry(ti, &tz->thermal_instances, tz_node) {
+ if (ti->cdev == new_cdev) {
+ last_tz = tz;
+ mutex_unlock(&thermal_list_lock);
+ thermal_check_zone_for_aggregation(tz);
+ /* because the lock was dropped ... */
+ goto start_over;
+ }
+ }
+ }
+
+ mutex_unlock(&thermal_list_lock);
+}
+
+#endif /* CONFIG_THERMAL_AGGREGATION_AUTO */
#endif /* CONFIG_THERMAL_AGGREGATION */
static void thermal_zone_device_resume(struct work_struct *work)