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