| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  *  thermal.c - sysfs interface of thermal devices | 
 |  * | 
 |  *  Copyright (C) 2016 Eduardo Valentin <edubezval@gmail.com> | 
 |  * | 
 |  *  Highly based on original thermal_core.c | 
 |  *  Copyright (C) 2008 Intel Corp | 
 |  *  Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> | 
 |  *  Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> | 
 |  */ | 
 |  | 
 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | 
 |  | 
 | #include <linux/sysfs.h> | 
 | #include <linux/device.h> | 
 | #include <linux/err.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/string.h> | 
 | #include <linux/jiffies.h> | 
 | #include <trace/hooks/thermal.h> | 
 |  | 
 | #include "thermal_core.h" | 
 |  | 
 | /* sys I/F for thermal zone */ | 
 |  | 
 | static ssize_t | 
 | type_show(struct device *dev, struct device_attribute *attr, char *buf) | 
 | { | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev); | 
 |  | 
 | 	return sprintf(buf, "%s\n", tz->type); | 
 | } | 
 |  | 
 | static ssize_t | 
 | temp_show(struct device *dev, struct device_attribute *attr, char *buf) | 
 | { | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev); | 
 | 	int temperature, ret; | 
 |  | 
 | 	ret = thermal_zone_get_temp(tz, &temperature); | 
 |  | 
 | 	if (!ret) | 
 | 		return sprintf(buf, "%d\n", temperature); | 
 |  | 
 | 	if (ret == -EAGAIN) | 
 | 		return -ENODATA; | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static ssize_t | 
 | mode_show(struct device *dev, struct device_attribute *attr, char *buf) | 
 | { | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev); | 
 | 	int enabled; | 
 |  | 
 | 	mutex_lock(&tz->lock); | 
 | 	enabled = thermal_zone_device_is_enabled(tz); | 
 | 	mutex_unlock(&tz->lock); | 
 |  | 
 | 	return sprintf(buf, "%s\n", enabled ? "enabled" : "disabled"); | 
 | } | 
 |  | 
 | static ssize_t | 
 | mode_store(struct device *dev, struct device_attribute *attr, | 
 | 	   const char *buf, size_t count) | 
 | { | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev); | 
 | 	int result; | 
 |  | 
 | 	if (!strncmp(buf, "enabled", sizeof("enabled") - 1)) | 
 | 		result = thermal_zone_device_enable(tz); | 
 | 	else if (!strncmp(buf, "disabled", sizeof("disabled") - 1)) | 
 | 		result = thermal_zone_device_disable(tz); | 
 | 	else | 
 | 		result = -EINVAL; | 
 |  | 
 | 	if (result) | 
 | 		return result; | 
 |  | 
 | 	return count; | 
 | } | 
 |  | 
 | static ssize_t | 
 | trip_point_type_show(struct device *dev, struct device_attribute *attr, | 
 | 		     char *buf) | 
 | { | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev); | 
 | 	enum thermal_trip_type type; | 
 | 	int trip, result; | 
 |  | 
 | 	if (!tz->ops->get_trip_type) | 
 | 		return -EPERM; | 
 |  | 
 | 	if (sscanf(attr->attr.name, "trip_point_%d_type", &trip) != 1) | 
 | 		return -EINVAL; | 
 |  | 
 | 	result = tz->ops->get_trip_type(tz, trip, &type); | 
 | 	if (result) | 
 | 		return result; | 
 |  | 
 | 	switch (type) { | 
 | 	case THERMAL_TRIP_CRITICAL: | 
 | 		return sprintf(buf, "critical\n"); | 
 | 	case THERMAL_TRIP_HOT: | 
 | 		return sprintf(buf, "hot\n"); | 
 | 	case THERMAL_TRIP_PASSIVE: | 
 | 		return sprintf(buf, "passive\n"); | 
 | 	case THERMAL_TRIP_ACTIVE: | 
 | 		return sprintf(buf, "active\n"); | 
 | 	default: | 
 | 		return sprintf(buf, "unknown\n"); | 
 | 	} | 
 | } | 
 |  | 
 | static ssize_t | 
 | trip_point_temp_store(struct device *dev, struct device_attribute *attr, | 
 | 		      const char *buf, size_t count) | 
 | { | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev); | 
 | 	int trip, ret; | 
 | 	int temperature, hyst = 0; | 
 | 	enum thermal_trip_type type; | 
 |  | 
 | 	if (!tz->ops->set_trip_temp && !tz->trips) | 
 | 		return -EPERM; | 
 |  | 
 | 	if (sscanf(attr->attr.name, "trip_point_%d_temp", &trip) != 1) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (kstrtoint(buf, 10, &temperature)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (tz->ops->set_trip_temp) { | 
 | 		ret = tz->ops->set_trip_temp(tz, trip, temperature); | 
 | 		if (ret) | 
 | 			return ret; | 
 | 	} | 
 |  | 
 | 	if (tz->trips) | 
 | 		tz->trips[trip].temperature = temperature; | 
 |  | 
 | 	if (tz->ops->get_trip_hyst) { | 
 | 		ret = tz->ops->get_trip_hyst(tz, trip, &hyst); | 
 | 		if (ret) | 
 | 			return ret; | 
 | 	} | 
 |  | 
 | 	ret = tz->ops->get_trip_type(tz, trip, &type); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	thermal_notify_tz_trip_change(tz->id, trip, type, temperature, hyst); | 
 |  | 
 | 	thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); | 
 |  | 
 | 	return count; | 
 | } | 
 |  | 
 | static ssize_t | 
 | trip_point_temp_show(struct device *dev, struct device_attribute *attr, | 
 | 		     char *buf) | 
 | { | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev); | 
 | 	int trip, ret; | 
 | 	int temperature; | 
 |  | 
 | 	if (!tz->ops->get_trip_temp) | 
 | 		return -EPERM; | 
 |  | 
 | 	if (sscanf(attr->attr.name, "trip_point_%d_temp", &trip) != 1) | 
 | 		return -EINVAL; | 
 |  | 
 | 	ret = tz->ops->get_trip_temp(tz, trip, &temperature); | 
 |  | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	return sprintf(buf, "%d\n", temperature); | 
 | } | 
 |  | 
 | static ssize_t | 
 | trip_point_hyst_store(struct device *dev, struct device_attribute *attr, | 
 | 		      const char *buf, size_t count) | 
 | { | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev); | 
 | 	int trip, ret; | 
 | 	int temperature; | 
 |  | 
 | 	if (!tz->ops->set_trip_hyst) | 
 | 		return -EPERM; | 
 |  | 
 | 	if (sscanf(attr->attr.name, "trip_point_%d_hyst", &trip) != 1) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (kstrtoint(buf, 10, &temperature)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	/* | 
 | 	 * We are not doing any check on the 'temperature' value | 
 | 	 * here. The driver implementing 'set_trip_hyst' has to | 
 | 	 * take care of this. | 
 | 	 */ | 
 | 	ret = tz->ops->set_trip_hyst(tz, trip, temperature); | 
 |  | 
 | 	if (!ret) | 
 | 		thermal_zone_set_trips(tz); | 
 |  | 
 | 	return ret ? ret : count; | 
 | } | 
 |  | 
 | static ssize_t | 
 | trip_point_hyst_show(struct device *dev, struct device_attribute *attr, | 
 | 		     char *buf) | 
 | { | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev); | 
 | 	int trip, ret; | 
 | 	int temperature; | 
 |  | 
 | 	if (!tz->ops->get_trip_hyst) | 
 | 		return -EPERM; | 
 |  | 
 | 	if (sscanf(attr->attr.name, "trip_point_%d_hyst", &trip) != 1) | 
 | 		return -EINVAL; | 
 |  | 
 | 	ret = tz->ops->get_trip_hyst(tz, trip, &temperature); | 
 |  | 
 | 	return ret ? ret : sprintf(buf, "%d\n", temperature); | 
 | } | 
 |  | 
 | static ssize_t | 
 | policy_store(struct device *dev, struct device_attribute *attr, | 
 | 	     const char *buf, size_t count) | 
 | { | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev); | 
 | 	char name[THERMAL_NAME_LENGTH]; | 
 | 	int ret; | 
 |  | 
 | 	snprintf(name, sizeof(name), "%s", buf); | 
 |  | 
 | 	ret = thermal_zone_device_set_policy(tz, name); | 
 | 	if (!ret) | 
 | 		ret = count; | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static ssize_t | 
 | policy_show(struct device *dev, struct device_attribute *devattr, char *buf) | 
 | { | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev); | 
 |  | 
 | 	return sprintf(buf, "%s\n", tz->governor->name); | 
 | } | 
 |  | 
 | static ssize_t | 
 | available_policies_show(struct device *dev, struct device_attribute *devattr, | 
 | 			char *buf) | 
 | { | 
 | 	return thermal_build_list_of_policies(buf); | 
 | } | 
 |  | 
 | #if (IS_ENABLED(CONFIG_THERMAL_EMULATION)) | 
 | static ssize_t | 
 | emul_temp_store(struct device *dev, struct device_attribute *attr, | 
 | 		const char *buf, size_t count) | 
 | { | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev); | 
 | 	int ret = 0; | 
 | 	int temperature; | 
 |  | 
 | 	if (kstrtoint(buf, 10, &temperature)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (!tz->ops->set_emul_temp) { | 
 | 		mutex_lock(&tz->lock); | 
 | 		tz->emul_temperature = temperature; | 
 | 		mutex_unlock(&tz->lock); | 
 | 	} else { | 
 | 		ret = tz->ops->set_emul_temp(tz, temperature); | 
 | 	} | 
 |  | 
 | 	if (!ret) | 
 | 		thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED); | 
 |  | 
 | 	return ret ? ret : count; | 
 | } | 
 | static DEVICE_ATTR_WO(emul_temp); | 
 | #endif | 
 |  | 
 | static ssize_t | 
 | sustainable_power_show(struct device *dev, struct device_attribute *devattr, | 
 | 		       char *buf) | 
 | { | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev); | 
 |  | 
 | 	if (tz->tzp) | 
 | 		return sprintf(buf, "%u\n", tz->tzp->sustainable_power); | 
 | 	else | 
 | 		return -EIO; | 
 | } | 
 |  | 
 | static ssize_t | 
 | sustainable_power_store(struct device *dev, struct device_attribute *devattr, | 
 | 			const char *buf, size_t count) | 
 | { | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev); | 
 | 	u32 sustainable_power; | 
 |  | 
 | 	if (!tz->tzp) | 
 | 		return -EIO; | 
 |  | 
 | 	if (kstrtou32(buf, 10, &sustainable_power)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	tz->tzp->sustainable_power = sustainable_power; | 
 |  | 
 | 	return count; | 
 | } | 
 |  | 
 | #define create_s32_tzp_attr(name)					\ | 
 | 	static ssize_t							\ | 
 | 	name##_show(struct device *dev, struct device_attribute *devattr, \ | 
 | 		char *buf)						\ | 
 | 	{								\ | 
 | 	struct thermal_zone_device *tz = to_thermal_zone(dev);		\ | 
 | 									\ | 
 | 	if (tz->tzp)							\ | 
 | 		return sprintf(buf, "%d\n", tz->tzp->name);		\ | 
 | 	else								\ | 
 | 		return -EIO;						\ | 
 | 	}								\ | 
 | 									\ | 
 | 	static ssize_t							\ | 
 | 	name##_store(struct device *dev, struct device_attribute *devattr, \ | 
 | 		const char *buf, size_t count)				\ | 
 | 	{								\ | 
 | 		struct thermal_zone_device *tz = to_thermal_zone(dev);	\ | 
 | 		s32 value;						\ | 
 | 									\ | 
 | 		if (!tz->tzp)						\ | 
 | 			return -EIO;					\ | 
 | 									\ | 
 | 		if (kstrtos32(buf, 10, &value))				\ | 
 | 			return -EINVAL;					\ | 
 | 									\ | 
 | 		tz->tzp->name = value;					\ | 
 | 									\ | 
 | 		return count;						\ | 
 | 	}								\ | 
 | 	static DEVICE_ATTR_RW(name) | 
 |  | 
 | create_s32_tzp_attr(k_po); | 
 | create_s32_tzp_attr(k_pu); | 
 | create_s32_tzp_attr(k_i); | 
 | create_s32_tzp_attr(k_d); | 
 | create_s32_tzp_attr(integral_cutoff); | 
 | create_s32_tzp_attr(slope); | 
 | create_s32_tzp_attr(offset); | 
 | #undef create_s32_tzp_attr | 
 |  | 
 | /* | 
 |  * These are thermal zone device attributes that will always be present. | 
 |  * All the attributes created for tzp (create_s32_tzp_attr) also are always | 
 |  * present on the sysfs interface. | 
 |  */ | 
 | static DEVICE_ATTR_RO(type); | 
 | static DEVICE_ATTR_RO(temp); | 
 | static DEVICE_ATTR_RW(policy); | 
 | static DEVICE_ATTR_RO(available_policies); | 
 | static DEVICE_ATTR_RW(sustainable_power); | 
 |  | 
 | /* These thermal zone device attributes are created based on conditions */ | 
 | static DEVICE_ATTR_RW(mode); | 
 |  | 
 | /* These attributes are unconditionally added to a thermal zone */ | 
 | static struct attribute *thermal_zone_dev_attrs[] = { | 
 | 	&dev_attr_type.attr, | 
 | 	&dev_attr_temp.attr, | 
 | #if (IS_ENABLED(CONFIG_THERMAL_EMULATION)) | 
 | 	&dev_attr_emul_temp.attr, | 
 | #endif | 
 | 	&dev_attr_policy.attr, | 
 | 	&dev_attr_available_policies.attr, | 
 | 	&dev_attr_sustainable_power.attr, | 
 | 	&dev_attr_k_po.attr, | 
 | 	&dev_attr_k_pu.attr, | 
 | 	&dev_attr_k_i.attr, | 
 | 	&dev_attr_k_d.attr, | 
 | 	&dev_attr_integral_cutoff.attr, | 
 | 	&dev_attr_slope.attr, | 
 | 	&dev_attr_offset.attr, | 
 | 	NULL, | 
 | }; | 
 |  | 
 | static const struct attribute_group thermal_zone_attribute_group = { | 
 | 	.attrs = thermal_zone_dev_attrs, | 
 | }; | 
 |  | 
 | static struct attribute *thermal_zone_mode_attrs[] = { | 
 | 	&dev_attr_mode.attr, | 
 | 	NULL, | 
 | }; | 
 |  | 
 | static const struct attribute_group thermal_zone_mode_attribute_group = { | 
 | 	.attrs = thermal_zone_mode_attrs, | 
 | }; | 
 |  | 
 | static const struct attribute_group *thermal_zone_attribute_groups[] = { | 
 | 	&thermal_zone_attribute_group, | 
 | 	&thermal_zone_mode_attribute_group, | 
 | 	/* This is not NULL terminated as we create the group dynamically */ | 
 | }; | 
 |  | 
 | /** | 
 |  * create_trip_attrs() - create attributes for trip points | 
 |  * @tz:		the thermal zone device | 
 |  * @mask:	Writeable trip point bitmap. | 
 |  * | 
 |  * helper function to instantiate sysfs entries for every trip | 
 |  * point and its properties of a struct thermal_zone_device. | 
 |  * | 
 |  * Return: 0 on success, the proper error value otherwise. | 
 |  */ | 
 | static int create_trip_attrs(struct thermal_zone_device *tz, int mask) | 
 | { | 
 | 	struct attribute **attrs; | 
 | 	int indx; | 
 |  | 
 | 	/* This function works only for zones with at least one trip */ | 
 | 	if (tz->num_trips <= 0) | 
 | 		return -EINVAL; | 
 |  | 
 | 	tz->trip_type_attrs = kcalloc(tz->num_trips, sizeof(*tz->trip_type_attrs), | 
 | 				      GFP_KERNEL); | 
 | 	if (!tz->trip_type_attrs) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	tz->trip_temp_attrs = kcalloc(tz->num_trips, sizeof(*tz->trip_temp_attrs), | 
 | 				      GFP_KERNEL); | 
 | 	if (!tz->trip_temp_attrs) { | 
 | 		kfree(tz->trip_type_attrs); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	if (tz->ops->get_trip_hyst) { | 
 | 		tz->trip_hyst_attrs = kcalloc(tz->num_trips, | 
 | 					      sizeof(*tz->trip_hyst_attrs), | 
 | 					      GFP_KERNEL); | 
 | 		if (!tz->trip_hyst_attrs) { | 
 | 			kfree(tz->trip_type_attrs); | 
 | 			kfree(tz->trip_temp_attrs); | 
 | 			return -ENOMEM; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	attrs = kcalloc(tz->num_trips * 3 + 1, sizeof(*attrs), GFP_KERNEL); | 
 | 	if (!attrs) { | 
 | 		kfree(tz->trip_type_attrs); | 
 | 		kfree(tz->trip_temp_attrs); | 
 | 		if (tz->ops->get_trip_hyst) | 
 | 			kfree(tz->trip_hyst_attrs); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	for (indx = 0; indx < tz->num_trips; indx++) { | 
 | 		/* create trip type attribute */ | 
 | 		snprintf(tz->trip_type_attrs[indx].name, THERMAL_NAME_LENGTH, | 
 | 			 "trip_point_%d_type", indx); | 
 |  | 
 | 		sysfs_attr_init(&tz->trip_type_attrs[indx].attr.attr); | 
 | 		tz->trip_type_attrs[indx].attr.attr.name = | 
 | 						tz->trip_type_attrs[indx].name; | 
 | 		tz->trip_type_attrs[indx].attr.attr.mode = S_IRUGO; | 
 | 		tz->trip_type_attrs[indx].attr.show = trip_point_type_show; | 
 | 		attrs[indx] = &tz->trip_type_attrs[indx].attr.attr; | 
 |  | 
 | 		/* create trip temp attribute */ | 
 | 		snprintf(tz->trip_temp_attrs[indx].name, THERMAL_NAME_LENGTH, | 
 | 			 "trip_point_%d_temp", indx); | 
 |  | 
 | 		sysfs_attr_init(&tz->trip_temp_attrs[indx].attr.attr); | 
 | 		tz->trip_temp_attrs[indx].attr.attr.name = | 
 | 						tz->trip_temp_attrs[indx].name; | 
 | 		tz->trip_temp_attrs[indx].attr.attr.mode = S_IRUGO; | 
 | 		tz->trip_temp_attrs[indx].attr.show = trip_point_temp_show; | 
 | 		if (IS_ENABLED(CONFIG_THERMAL_WRITABLE_TRIPS) && | 
 | 		    mask & (1 << indx)) { | 
 | 			tz->trip_temp_attrs[indx].attr.attr.mode |= S_IWUSR; | 
 | 			tz->trip_temp_attrs[indx].attr.store = | 
 | 							trip_point_temp_store; | 
 | 		} | 
 | 		attrs[indx + tz->num_trips] = &tz->trip_temp_attrs[indx].attr.attr; | 
 |  | 
 | 		/* create Optional trip hyst attribute */ | 
 | 		if (!tz->ops->get_trip_hyst) | 
 | 			continue; | 
 | 		snprintf(tz->trip_hyst_attrs[indx].name, THERMAL_NAME_LENGTH, | 
 | 			 "trip_point_%d_hyst", indx); | 
 |  | 
 | 		sysfs_attr_init(&tz->trip_hyst_attrs[indx].attr.attr); | 
 | 		tz->trip_hyst_attrs[indx].attr.attr.name = | 
 | 					tz->trip_hyst_attrs[indx].name; | 
 | 		tz->trip_hyst_attrs[indx].attr.attr.mode = S_IRUGO; | 
 | 		tz->trip_hyst_attrs[indx].attr.show = trip_point_hyst_show; | 
 | 		if (tz->ops->set_trip_hyst) { | 
 | 			tz->trip_hyst_attrs[indx].attr.attr.mode |= S_IWUSR; | 
 | 			tz->trip_hyst_attrs[indx].attr.store = | 
 | 					trip_point_hyst_store; | 
 | 		} | 
 | 		attrs[indx + tz->num_trips * 2] = | 
 | 					&tz->trip_hyst_attrs[indx].attr.attr; | 
 | 	} | 
 | 	attrs[tz->num_trips * 3] = NULL; | 
 |  | 
 | 	tz->trips_attribute_group.attrs = attrs; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /** | 
 |  * destroy_trip_attrs() - destroy attributes for trip points | 
 |  * @tz:		the thermal zone device | 
 |  * | 
 |  * helper function to free resources allocated by create_trip_attrs() | 
 |  */ | 
 | static void destroy_trip_attrs(struct thermal_zone_device *tz) | 
 | { | 
 | 	if (!tz) | 
 | 		return; | 
 |  | 
 | 	kfree(tz->trip_type_attrs); | 
 | 	kfree(tz->trip_temp_attrs); | 
 | 	if (tz->ops->get_trip_hyst) | 
 | 		kfree(tz->trip_hyst_attrs); | 
 | 	kfree(tz->trips_attribute_group.attrs); | 
 | } | 
 |  | 
 | int thermal_zone_create_device_groups(struct thermal_zone_device *tz, | 
 | 				      int mask) | 
 | { | 
 | 	const struct attribute_group **groups; | 
 | 	int i, size, result; | 
 |  | 
 | 	/* we need one extra for trips and the NULL to terminate the array */ | 
 | 	size = ARRAY_SIZE(thermal_zone_attribute_groups) + 2; | 
 | 	/* This also takes care of API requirement to be NULL terminated */ | 
 | 	groups = kcalloc(size, sizeof(*groups), GFP_KERNEL); | 
 | 	if (!groups) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	for (i = 0; i < size - 2; i++) | 
 | 		groups[i] = thermal_zone_attribute_groups[i]; | 
 |  | 
 | 	if (tz->num_trips) { | 
 | 		result = create_trip_attrs(tz, mask); | 
 | 		if (result) { | 
 | 			kfree(groups); | 
 |  | 
 | 			return result; | 
 | 		} | 
 |  | 
 | 		groups[size - 2] = &tz->trips_attribute_group; | 
 | 	} | 
 |  | 
 | 	tz->device.groups = groups; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | void thermal_zone_destroy_device_groups(struct thermal_zone_device *tz) | 
 | { | 
 | 	if (!tz) | 
 | 		return; | 
 |  | 
 | 	if (tz->num_trips) | 
 | 		destroy_trip_attrs(tz); | 
 |  | 
 | 	kfree(tz->device.groups); | 
 | } | 
 |  | 
 | /* sys I/F for cooling device */ | 
 | static ssize_t | 
 | cdev_type_show(struct device *dev, struct device_attribute *attr, char *buf) | 
 | { | 
 | 	struct thermal_cooling_device *cdev = to_cooling_device(dev); | 
 |  | 
 | 	return sprintf(buf, "%s\n", cdev->type); | 
 | } | 
 |  | 
 | static ssize_t max_state_show(struct device *dev, struct device_attribute *attr, | 
 | 			      char *buf) | 
 | { | 
 | 	struct thermal_cooling_device *cdev = to_cooling_device(dev); | 
 |  | 
 | 	return sprintf(buf, "%ld\n", cdev->max_state); | 
 | } | 
 |  | 
 | static ssize_t cur_state_show(struct device *dev, struct device_attribute *attr, | 
 | 			      char *buf) | 
 | { | 
 | 	struct thermal_cooling_device *cdev = to_cooling_device(dev); | 
 | 	unsigned long state; | 
 | 	int ret; | 
 |  | 
 | 	ret = cdev->ops->get_cur_state(cdev, &state); | 
 | 	if (ret) | 
 | 		return ret; | 
 | 	return sprintf(buf, "%ld\n", state); | 
 | } | 
 |  | 
 | static ssize_t | 
 | cur_state_store(struct device *dev, struct device_attribute *attr, | 
 | 		const char *buf, size_t count) | 
 | { | 
 | 	struct thermal_cooling_device *cdev = to_cooling_device(dev); | 
 | 	unsigned long state; | 
 | 	int result; | 
 |  | 
 | 	if (sscanf(buf, "%ld\n", &state) != 1) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if ((long)state < 0) | 
 | 		return -EINVAL; | 
 |  | 
 | 	/* Requested state should be less than max_state + 1 */ | 
 | 	if (state > cdev->max_state) | 
 | 		return -EINVAL; | 
 |  | 
 | 	mutex_lock(&cdev->lock); | 
 |  | 
 | 	result = cdev->ops->set_cur_state(cdev, state); | 
 | 	if (!result) | 
 | 		thermal_cooling_device_stats_update(cdev, state); | 
 |  | 
 | 	mutex_unlock(&cdev->lock); | 
 | 	return result ? result : count; | 
 | } | 
 |  | 
 | static struct device_attribute | 
 | dev_attr_cdev_type = __ATTR(type, 0444, cdev_type_show, NULL); | 
 | static DEVICE_ATTR_RO(max_state); | 
 | static DEVICE_ATTR_RW(cur_state); | 
 |  | 
 | static struct attribute *cooling_device_attrs[] = { | 
 | 	&dev_attr_cdev_type.attr, | 
 | 	&dev_attr_max_state.attr, | 
 | 	&dev_attr_cur_state.attr, | 
 | 	NULL, | 
 | }; | 
 |  | 
 | static const struct attribute_group cooling_device_attr_group = { | 
 | 	.attrs = cooling_device_attrs, | 
 | }; | 
 |  | 
 | static const struct attribute_group *cooling_device_attr_groups[] = { | 
 | 	&cooling_device_attr_group, | 
 | 	NULL, /* Space allocated for cooling_device_stats_attr_group */ | 
 | 	NULL, | 
 | }; | 
 |  | 
 | #ifdef CONFIG_THERMAL_STATISTICS | 
 | struct cooling_dev_stats { | 
 | 	spinlock_t lock; | 
 | 	unsigned int total_trans; | 
 | 	unsigned long state; | 
 | 	unsigned long max_states; | 
 | 	ktime_t last_time; | 
 | 	ktime_t *time_in_state; | 
 | 	unsigned int *trans_table; | 
 | }; | 
 |  | 
 | static void update_time_in_state(struct cooling_dev_stats *stats) | 
 | { | 
 | 	ktime_t now = ktime_get(), delta; | 
 |  | 
 | 	delta = ktime_sub(now, stats->last_time); | 
 | 	stats->time_in_state[stats->state] = | 
 | 		ktime_add(stats->time_in_state[stats->state], delta); | 
 | 	stats->last_time = now; | 
 | } | 
 |  | 
 | void thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev, | 
 | 					 unsigned long new_state) | 
 | { | 
 | 	struct cooling_dev_stats *stats = cdev->stats; | 
 |  | 
 | 	if (!stats) | 
 | 		return; | 
 |  | 
 | 	spin_lock(&stats->lock); | 
 |  | 
 | 	if (stats->state == new_state) | 
 | 		goto unlock; | 
 |  | 
 | 	update_time_in_state(stats); | 
 | 	stats->trans_table[stats->state * stats->max_states + new_state]++; | 
 | 	stats->state = new_state; | 
 | 	stats->total_trans++; | 
 |  | 
 | unlock: | 
 | 	spin_unlock(&stats->lock); | 
 | } | 
 |  | 
 | static ssize_t total_trans_show(struct device *dev, | 
 | 				struct device_attribute *attr, char *buf) | 
 | { | 
 | 	struct thermal_cooling_device *cdev = to_cooling_device(dev); | 
 | 	struct cooling_dev_stats *stats = cdev->stats; | 
 | 	int ret; | 
 |  | 
 | 	spin_lock(&stats->lock); | 
 | 	ret = sprintf(buf, "%u\n", stats->total_trans); | 
 | 	spin_unlock(&stats->lock); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static ssize_t | 
 | time_in_state_ms_show(struct device *dev, struct device_attribute *attr, | 
 | 		      char *buf) | 
 | { | 
 | 	struct thermal_cooling_device *cdev = to_cooling_device(dev); | 
 | 	struct cooling_dev_stats *stats = cdev->stats; | 
 | 	ssize_t len = 0; | 
 | 	int i; | 
 |  | 
 | 	spin_lock(&stats->lock); | 
 | 	update_time_in_state(stats); | 
 |  | 
 | 	for (i = 0; i < stats->max_states; i++) { | 
 | 		len += sprintf(buf + len, "state%u\t%llu\n", i, | 
 | 			       ktime_to_ms(stats->time_in_state[i])); | 
 | 	} | 
 | 	spin_unlock(&stats->lock); | 
 |  | 
 | 	return len; | 
 | } | 
 |  | 
 | static ssize_t | 
 | reset_store(struct device *dev, struct device_attribute *attr, const char *buf, | 
 | 	    size_t count) | 
 | { | 
 | 	struct thermal_cooling_device *cdev = to_cooling_device(dev); | 
 | 	struct cooling_dev_stats *stats = cdev->stats; | 
 | 	int i, states = stats->max_states; | 
 |  | 
 | 	spin_lock(&stats->lock); | 
 |  | 
 | 	stats->total_trans = 0; | 
 | 	stats->last_time = ktime_get(); | 
 | 	memset(stats->trans_table, 0, | 
 | 	       states * states * sizeof(*stats->trans_table)); | 
 |  | 
 | 	for (i = 0; i < stats->max_states; i++) | 
 | 		stats->time_in_state[i] = ktime_set(0, 0); | 
 |  | 
 | 	spin_unlock(&stats->lock); | 
 |  | 
 | 	return count; | 
 | } | 
 |  | 
 | static ssize_t trans_table_show(struct device *dev, | 
 | 				struct device_attribute *attr, char *buf) | 
 | { | 
 | 	struct thermal_cooling_device *cdev = to_cooling_device(dev); | 
 | 	struct cooling_dev_stats *stats = cdev->stats; | 
 | 	ssize_t len = 0; | 
 | 	int i, j; | 
 |  | 
 | 	len += snprintf(buf + len, PAGE_SIZE - len, " From  :    To\n"); | 
 | 	len += snprintf(buf + len, PAGE_SIZE - len, "       : "); | 
 | 	for (i = 0; i < stats->max_states; i++) { | 
 | 		if (len >= PAGE_SIZE) | 
 | 			break; | 
 | 		len += snprintf(buf + len, PAGE_SIZE - len, "state%2u  ", i); | 
 | 	} | 
 | 	if (len >= PAGE_SIZE) | 
 | 		return PAGE_SIZE; | 
 |  | 
 | 	len += snprintf(buf + len, PAGE_SIZE - len, "\n"); | 
 |  | 
 | 	for (i = 0; i < stats->max_states; i++) { | 
 | 		if (len >= PAGE_SIZE) | 
 | 			break; | 
 |  | 
 | 		len += snprintf(buf + len, PAGE_SIZE - len, "state%2u:", i); | 
 |  | 
 | 		for (j = 0; j < stats->max_states; j++) { | 
 | 			if (len >= PAGE_SIZE) | 
 | 				break; | 
 | 			len += snprintf(buf + len, PAGE_SIZE - len, "%8u ", | 
 | 				stats->trans_table[i * stats->max_states + j]); | 
 | 		} | 
 | 		if (len >= PAGE_SIZE) | 
 | 			break; | 
 | 		len += snprintf(buf + len, PAGE_SIZE - len, "\n"); | 
 | 	} | 
 |  | 
 | 	if (len >= PAGE_SIZE) { | 
 | 		pr_warn_once("Thermal transition table exceeds PAGE_SIZE. Disabling\n"); | 
 | 		return -EFBIG; | 
 | 	} | 
 | 	return len; | 
 | } | 
 |  | 
 | static DEVICE_ATTR_RO(total_trans); | 
 | static DEVICE_ATTR_RO(time_in_state_ms); | 
 | static DEVICE_ATTR_WO(reset); | 
 | static DEVICE_ATTR_RO(trans_table); | 
 |  | 
 | static struct attribute *cooling_device_stats_attrs[] = { | 
 | 	&dev_attr_total_trans.attr, | 
 | 	&dev_attr_time_in_state_ms.attr, | 
 | 	&dev_attr_reset.attr, | 
 | 	&dev_attr_trans_table.attr, | 
 | 	NULL | 
 | }; | 
 |  | 
 | static const struct attribute_group cooling_device_stats_attr_group = { | 
 | 	.attrs = cooling_device_stats_attrs, | 
 | 	.name = "stats" | 
 | }; | 
 |  | 
 | static void cooling_device_stats_setup(struct thermal_cooling_device *cdev) | 
 | { | 
 | 	const struct attribute_group *stats_attr_group = NULL; | 
 | 	struct cooling_dev_stats *stats; | 
 | 	unsigned long states; | 
 | 	int var; | 
 | 	bool disable_cdev_stats = false; | 
 |  | 
 | 	trace_android_vh_disable_thermal_cooling_stats(cdev, | 
 | 						&disable_cdev_stats); | 
 | 	if (disable_cdev_stats) | 
 | 		goto out; | 
 |  | 
 | 	if (cdev->ops->get_max_state(cdev, &states)) | 
 | 		goto out; | 
 |  | 
 | 	states++; /* Total number of states is highest state + 1 */ | 
 |  | 
 | 	var = sizeof(*stats); | 
 | 	var += sizeof(*stats->time_in_state) * states; | 
 | 	var += sizeof(*stats->trans_table) * states * states; | 
 |  | 
 | 	stats = kzalloc(var, GFP_KERNEL); | 
 | 	if (!stats) | 
 | 		goto out; | 
 |  | 
 | 	stats->time_in_state = (ktime_t *)(stats + 1); | 
 | 	stats->trans_table = (unsigned int *)(stats->time_in_state + states); | 
 | 	cdev->stats = stats; | 
 | 	stats->last_time = ktime_get(); | 
 | 	stats->max_states = states; | 
 |  | 
 | 	spin_lock_init(&stats->lock); | 
 |  | 
 | 	stats_attr_group = &cooling_device_stats_attr_group; | 
 |  | 
 | out: | 
 | 	/* Fill the empty slot left in cooling_device_attr_groups */ | 
 | 	var = ARRAY_SIZE(cooling_device_attr_groups) - 2; | 
 | 	cooling_device_attr_groups[var] = stats_attr_group; | 
 | } | 
 |  | 
 | static void cooling_device_stats_destroy(struct thermal_cooling_device *cdev) | 
 | { | 
 | 	kfree(cdev->stats); | 
 | 	cdev->stats = NULL; | 
 | } | 
 |  | 
 | #else | 
 |  | 
 | static inline void | 
 | cooling_device_stats_setup(struct thermal_cooling_device *cdev) {} | 
 | static inline void | 
 | cooling_device_stats_destroy(struct thermal_cooling_device *cdev) {} | 
 |  | 
 | #endif /* CONFIG_THERMAL_STATISTICS */ | 
 |  | 
 | void thermal_cooling_device_setup_sysfs(struct thermal_cooling_device *cdev) | 
 | { | 
 | 	cooling_device_stats_setup(cdev); | 
 | 	cdev->device.groups = cooling_device_attr_groups; | 
 | } | 
 |  | 
 | void thermal_cooling_device_destroy_sysfs(struct thermal_cooling_device *cdev) | 
 | { | 
 | 	cooling_device_stats_destroy(cdev); | 
 | } | 
 |  | 
 | /* these helper will be used only at the time of bindig */ | 
 | ssize_t | 
 | trip_point_show(struct device *dev, struct device_attribute *attr, char *buf) | 
 | { | 
 | 	struct thermal_instance *instance; | 
 |  | 
 | 	instance = | 
 | 	    container_of(attr, struct thermal_instance, attr); | 
 |  | 
 | 	return sprintf(buf, "%d\n", instance->trip); | 
 | } | 
 |  | 
 | ssize_t | 
 | weight_show(struct device *dev, struct device_attribute *attr, char *buf) | 
 | { | 
 | 	struct thermal_instance *instance; | 
 |  | 
 | 	instance = container_of(attr, struct thermal_instance, weight_attr); | 
 |  | 
 | 	return sprintf(buf, "%d\n", instance->weight); | 
 | } | 
 |  | 
 | ssize_t weight_store(struct device *dev, struct device_attribute *attr, | 
 | 		     const char *buf, size_t count) | 
 | { | 
 | 	struct thermal_instance *instance; | 
 | 	int ret, weight; | 
 |  | 
 | 	ret = kstrtoint(buf, 0, &weight); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	instance = container_of(attr, struct thermal_instance, weight_attr); | 
 | 	instance->weight = weight; | 
 |  | 
 | 	return count; | 
 | } |