1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * thermal_hwmon.c - Generic Thermal Management hwmon support. 4 * 5 * Code based on Intel thermal_core.c. Copyrights of the original code: 6 * Copyright (C) 2008 Intel Corp 7 * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> 8 * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> 9 * 10 * Copyright (C) 2013 Texas Instruments 11 * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com> 12 */ 13 #include <linux/err.h> 14 #include <linux/export.h> 15 #include <linux/hwmon.h> 16 #include <linux/slab.h> 17 #include <linux/thermal.h> 18 19 #include "thermal_hwmon.h" 20 #include "thermal_core.h" 21 22 /* 23 * Needs to be large enough to hold a thermal zone type string followed by an 24 * underline character and a 32-bit integer in decimal representation. 25 */ 26 #define THERMAL_HWMON_NAME_LENGTH (THERMAL_NAME_LENGTH + 11) 27 28 /* hwmon sys I/F */ 29 /* thermal zone devices with the same type share one hwmon device */ 30 struct thermal_hwmon_device { 31 char name[THERMAL_HWMON_NAME_LENGTH]; 32 struct device *device; 33 struct list_head node; 34 struct thermal_zone_device *tz; 35 }; 36 37 static LIST_HEAD(thermal_hwmon_list); 38 39 static DEFINE_MUTEX(thermal_hwmon_list_lock); 40 41 static ssize_t 42 temp1_input_show(struct device *dev, struct device_attribute *attr, char *buf) 43 { 44 struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev); 45 struct thermal_zone_device *tz = hwmon->tz; 46 int temperature; 47 int ret; 48 49 ret = thermal_zone_get_temp(tz, &temperature); 50 if (ret) 51 return ret; 52 53 return sysfs_emit(buf, "%d\n", temperature); 54 } 55 56 static ssize_t 57 temp1_crit_show(struct device *dev, struct device_attribute *attr, char *buf) 58 { 59 struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev); 60 struct thermal_zone_device *tz = hwmon->tz; 61 int temperature; 62 int ret; 63 64 guard(thermal_zone)(tz); 65 66 ret = tz->ops.get_crit_temp(tz, &temperature); 67 if (ret) 68 return ret; 69 70 return sysfs_emit(buf, "%d\n", temperature); 71 } 72 73 static DEVICE_ATTR_RO(temp1_input); 74 static DEVICE_ATTR_RO(temp1_crit); 75 76 static struct attribute *thermal_hwmon_attrs[] = { 77 &dev_attr_temp1_input.attr, 78 &dev_attr_temp1_crit.attr, 79 NULL, 80 }; 81 82 static umode_t thermal_hwmon_attr_is_visible(struct kobject *kobj, 83 struct attribute *a, int n) 84 { 85 if (a == &dev_attr_temp1_input.attr) 86 return a->mode; 87 88 if (a == &dev_attr_temp1_crit.attr) { 89 struct thermal_hwmon_device *hwmon = dev_get_drvdata(kobj_to_dev(kobj)); 90 struct thermal_zone_device *tz = hwmon->tz; 91 int dummy; 92 93 if (tz->ops.get_crit_temp && !tz->ops.get_crit_temp(tz, &dummy)) 94 return a->mode; 95 } 96 97 return 0; 98 } 99 100 static const struct attribute_group thermal_hwmon_group = { 101 .attrs = thermal_hwmon_attrs, 102 .is_visible = thermal_hwmon_attr_is_visible, 103 }; 104 105 __ATTRIBUTE_GROUPS(thermal_hwmon); 106 107 int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) 108 { 109 struct thermal_hwmon_device *hwmon; 110 111 hwmon = kzalloc_obj(*hwmon); 112 if (!hwmon) 113 return -ENOMEM; 114 115 hwmon->tz = tz; 116 /* 117 * Append the thermal zone ID preceded by an underline character to the 118 * type to disambiguate the sensors command output. 119 */ 120 scnprintf(hwmon->name, THERMAL_HWMON_NAME_LENGTH, "%s_%d", tz->type, tz->id); 121 strreplace(hwmon->name, '-', '_'); 122 hwmon->device = hwmon_device_register_for_thermal(&tz->device, 123 hwmon->name, hwmon, 124 thermal_hwmon_groups); 125 if (IS_ERR(hwmon->device)) { 126 int result = PTR_ERR(hwmon->device); 127 128 kfree(hwmon); 129 return result; 130 } 131 132 /* The list is needed for hwmon lookup during removal. */ 133 mutex_lock(&thermal_hwmon_list_lock); 134 list_add_tail(&hwmon->node, &thermal_hwmon_list); 135 mutex_unlock(&thermal_hwmon_list_lock); 136 137 return 0; 138 } 139 EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); 140 141 static struct thermal_hwmon_device * 142 thermal_hwmon_lookup(const struct thermal_zone_device *tz) 143 { 144 struct thermal_hwmon_device *hwmon; 145 146 list_for_each_entry(hwmon, &thermal_hwmon_list, node) { 147 if (hwmon->tz == tz) 148 return hwmon; 149 } 150 return NULL; 151 } 152 153 void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) 154 { 155 struct thermal_hwmon_device *hwmon; 156 157 scoped_guard(mutex, &thermal_hwmon_list_lock) { 158 hwmon = thermal_hwmon_lookup(tz); 159 if (!hwmon) 160 return; 161 162 list_del(&hwmon->node); 163 } 164 165 hwmon_device_unregister(hwmon->device); 166 kfree(hwmon); 167 } 168 EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs); 169 170 static void devm_thermal_hwmon_release(struct device *dev, void *res) 171 { 172 thermal_remove_hwmon_sysfs(*(struct thermal_zone_device **)res); 173 } 174 175 int devm_thermal_add_hwmon_sysfs(struct device *dev, struct thermal_zone_device *tz) 176 { 177 struct thermal_zone_device **ptr; 178 int ret; 179 180 ptr = devres_alloc(devm_thermal_hwmon_release, sizeof(*ptr), 181 GFP_KERNEL); 182 if (!ptr) { 183 dev_warn(dev, "Failed to allocate device resource data\n"); 184 return -ENOMEM; 185 } 186 187 ret = thermal_add_hwmon_sysfs(tz); 188 if (ret) { 189 dev_warn(dev, "Failed to add hwmon sysfs attributes\n"); 190 devres_free(ptr); 191 return ret; 192 } 193 194 *ptr = tz; 195 devres_add(dev, ptr); 196 197 return ret; 198 } 199 EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs); 200 201 MODULE_IMPORT_NS("HWMON_THERMAL"); 202