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 /* hwmon sys I/F */ 23 /* thermal zone devices with the same type share one hwmon device */ 24 struct thermal_hwmon_device { 25 char type[THERMAL_NAME_LENGTH]; 26 struct device *device; 27 int count; 28 struct list_head tz_list; 29 struct list_head node; 30 }; 31 32 struct thermal_hwmon_attr { 33 struct device_attribute attr; 34 char name[16]; 35 }; 36 37 /* one temperature input for each thermal zone */ 38 struct thermal_hwmon_temp { 39 struct list_head hwmon_node; 40 struct thermal_zone_device *tz; 41 struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ 42 struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ 43 }; 44 45 static LIST_HEAD(thermal_hwmon_list); 46 47 static DEFINE_MUTEX(thermal_hwmon_list_lock); 48 49 static ssize_t 50 temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) 51 { 52 int temperature; 53 int ret; 54 struct thermal_hwmon_attr *hwmon_attr 55 = container_of(attr, struct thermal_hwmon_attr, attr); 56 struct thermal_hwmon_temp *temp 57 = container_of(hwmon_attr, struct thermal_hwmon_temp, 58 temp_input); 59 struct thermal_zone_device *tz = temp->tz; 60 61 ret = thermal_zone_get_temp(tz, &temperature); 62 63 if (ret) 64 return ret; 65 66 return sprintf(buf, "%d\n", temperature); 67 } 68 69 static ssize_t 70 temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) 71 { 72 struct thermal_hwmon_attr *hwmon_attr 73 = container_of(attr, struct thermal_hwmon_attr, attr); 74 struct thermal_hwmon_temp *temp 75 = container_of(hwmon_attr, struct thermal_hwmon_temp, 76 temp_crit); 77 struct thermal_zone_device *tz = temp->tz; 78 int temperature; 79 int ret; 80 81 mutex_lock(&tz->lock); 82 83 if (device_is_registered(&tz->device)) 84 ret = tz->ops->get_crit_temp(tz, &temperature); 85 else 86 ret = -ENODEV; 87 88 mutex_unlock(&tz->lock); 89 90 if (ret) 91 return ret; 92 93 return sprintf(buf, "%d\n", temperature); 94 } 95 96 97 static struct thermal_hwmon_device * 98 thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) 99 { 100 struct thermal_hwmon_device *hwmon; 101 char type[THERMAL_NAME_LENGTH]; 102 103 mutex_lock(&thermal_hwmon_list_lock); 104 list_for_each_entry(hwmon, &thermal_hwmon_list, node) { 105 strcpy(type, tz->type); 106 strreplace(type, '-', '_'); 107 if (!strcmp(hwmon->type, type)) { 108 mutex_unlock(&thermal_hwmon_list_lock); 109 return hwmon; 110 } 111 } 112 mutex_unlock(&thermal_hwmon_list_lock); 113 114 return NULL; 115 } 116 117 /* Find the temperature input matching a given thermal zone */ 118 static struct thermal_hwmon_temp * 119 thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, 120 const struct thermal_zone_device *tz) 121 { 122 struct thermal_hwmon_temp *temp; 123 124 mutex_lock(&thermal_hwmon_list_lock); 125 list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) 126 if (temp->tz == tz) { 127 mutex_unlock(&thermal_hwmon_list_lock); 128 return temp; 129 } 130 mutex_unlock(&thermal_hwmon_list_lock); 131 132 return NULL; 133 } 134 135 static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) 136 { 137 int temp; 138 return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp); 139 } 140 141 int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) 142 { 143 struct thermal_hwmon_device *hwmon; 144 struct thermal_hwmon_temp *temp; 145 int new_hwmon_device = 1; 146 int result; 147 148 hwmon = thermal_hwmon_lookup_by_type(tz); 149 if (hwmon) { 150 new_hwmon_device = 0; 151 goto register_sys_interface; 152 } 153 154 hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL); 155 if (!hwmon) 156 return -ENOMEM; 157 158 INIT_LIST_HEAD(&hwmon->tz_list); 159 strscpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); 160 strreplace(hwmon->type, '-', '_'); 161 hwmon->device = hwmon_device_register_for_thermal(&tz->device, 162 hwmon->type, hwmon); 163 if (IS_ERR(hwmon->device)) { 164 result = PTR_ERR(hwmon->device); 165 goto free_mem; 166 } 167 168 register_sys_interface: 169 temp = kzalloc(sizeof(*temp), GFP_KERNEL); 170 if (!temp) { 171 result = -ENOMEM; 172 goto unregister_name; 173 } 174 175 temp->tz = tz; 176 hwmon->count++; 177 178 snprintf(temp->temp_input.name, sizeof(temp->temp_input.name), 179 "temp%d_input", hwmon->count); 180 temp->temp_input.attr.attr.name = temp->temp_input.name; 181 temp->temp_input.attr.attr.mode = 0444; 182 temp->temp_input.attr.show = temp_input_show; 183 sysfs_attr_init(&temp->temp_input.attr.attr); 184 result = device_create_file(hwmon->device, &temp->temp_input.attr); 185 if (result) 186 goto free_temp_mem; 187 188 if (thermal_zone_crit_temp_valid(tz)) { 189 snprintf(temp->temp_crit.name, 190 sizeof(temp->temp_crit.name), 191 "temp%d_crit", hwmon->count); 192 temp->temp_crit.attr.attr.name = temp->temp_crit.name; 193 temp->temp_crit.attr.attr.mode = 0444; 194 temp->temp_crit.attr.show = temp_crit_show; 195 sysfs_attr_init(&temp->temp_crit.attr.attr); 196 result = device_create_file(hwmon->device, 197 &temp->temp_crit.attr); 198 if (result) 199 goto unregister_input; 200 } 201 202 mutex_lock(&thermal_hwmon_list_lock); 203 if (new_hwmon_device) 204 list_add_tail(&hwmon->node, &thermal_hwmon_list); 205 list_add_tail(&temp->hwmon_node, &hwmon->tz_list); 206 mutex_unlock(&thermal_hwmon_list_lock); 207 208 return 0; 209 210 unregister_input: 211 device_remove_file(hwmon->device, &temp->temp_input.attr); 212 free_temp_mem: 213 kfree(temp); 214 unregister_name: 215 if (new_hwmon_device) 216 hwmon_device_unregister(hwmon->device); 217 free_mem: 218 kfree(hwmon); 219 220 return result; 221 } 222 EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); 223 224 void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) 225 { 226 struct thermal_hwmon_device *hwmon; 227 struct thermal_hwmon_temp *temp; 228 229 hwmon = thermal_hwmon_lookup_by_type(tz); 230 if (unlikely(!hwmon)) { 231 /* Should never happen... */ 232 dev_dbg(&tz->device, "hwmon device lookup failed!\n"); 233 return; 234 } 235 236 temp = thermal_hwmon_lookup_temp(hwmon, tz); 237 if (unlikely(!temp)) { 238 /* Should never happen... */ 239 dev_dbg(&tz->device, "temperature input lookup failed!\n"); 240 return; 241 } 242 243 device_remove_file(hwmon->device, &temp->temp_input.attr); 244 if (thermal_zone_crit_temp_valid(tz)) 245 device_remove_file(hwmon->device, &temp->temp_crit.attr); 246 247 mutex_lock(&thermal_hwmon_list_lock); 248 list_del(&temp->hwmon_node); 249 kfree(temp); 250 if (!list_empty(&hwmon->tz_list)) { 251 mutex_unlock(&thermal_hwmon_list_lock); 252 return; 253 } 254 list_del(&hwmon->node); 255 mutex_unlock(&thermal_hwmon_list_lock); 256 257 hwmon_device_unregister(hwmon->device); 258 kfree(hwmon); 259 } 260 EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs); 261 262 static void devm_thermal_hwmon_release(struct device *dev, void *res) 263 { 264 thermal_remove_hwmon_sysfs(*(struct thermal_zone_device **)res); 265 } 266 267 int devm_thermal_add_hwmon_sysfs(struct device *dev, struct thermal_zone_device *tz) 268 { 269 struct thermal_zone_device **ptr; 270 int ret; 271 272 ptr = devres_alloc(devm_thermal_hwmon_release, sizeof(*ptr), 273 GFP_KERNEL); 274 if (!ptr) { 275 dev_warn(dev, "Failed to allocate device resource data\n"); 276 return -ENOMEM; 277 } 278 279 ret = thermal_add_hwmon_sysfs(tz); 280 if (ret) { 281 dev_warn(dev, "Failed to add hwmon sysfs attributes\n"); 282 devres_free(ptr); 283 return ret; 284 } 285 286 *ptr = tz; 287 devres_add(dev, ptr); 288 289 return ret; 290 } 291 EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs); 292 293 MODULE_IMPORT_NS(HWMON_THERMAL); 294