17e3c0381SLina Iyer // SPDX-License-Identifier: GPL-2.0 20dd88793SEduardo Valentin /* 30dd88793SEduardo Valentin * thermal_hwmon.c - Generic Thermal Management hwmon support. 40dd88793SEduardo Valentin * 50dd88793SEduardo Valentin * Code based on Intel thermal_core.c. Copyrights of the original code: 60dd88793SEduardo Valentin * Copyright (C) 2008 Intel Corp 70dd88793SEduardo Valentin * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> 80dd88793SEduardo Valentin * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> 90dd88793SEduardo Valentin * 100dd88793SEduardo Valentin * Copyright (C) 2013 Texas Instruments 110dd88793SEduardo Valentin * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com> 120dd88793SEduardo Valentin */ 130dd88793SEduardo Valentin #include <linux/err.h> 14*e5ebf357SAmit Kucheria #include <linux/export.h> 151330e04fSAmit Kucheria #include <linux/hwmon.h> 161330e04fSAmit Kucheria #include <linux/slab.h> 171330e04fSAmit Kucheria #include <linux/thermal.h> 181330e04fSAmit Kucheria 190dd88793SEduardo Valentin #include "thermal_hwmon.h" 200dd88793SEduardo Valentin 210dd88793SEduardo Valentin /* hwmon sys I/F */ 220dd88793SEduardo Valentin /* thermal zone devices with the same type share one hwmon device */ 230dd88793SEduardo Valentin struct thermal_hwmon_device { 240dd88793SEduardo Valentin char type[THERMAL_NAME_LENGTH]; 250dd88793SEduardo Valentin struct device *device; 260dd88793SEduardo Valentin int count; 270dd88793SEduardo Valentin struct list_head tz_list; 280dd88793SEduardo Valentin struct list_head node; 290dd88793SEduardo Valentin }; 300dd88793SEduardo Valentin 310dd88793SEduardo Valentin struct thermal_hwmon_attr { 320dd88793SEduardo Valentin struct device_attribute attr; 330dd88793SEduardo Valentin char name[16]; 340dd88793SEduardo Valentin }; 350dd88793SEduardo Valentin 360dd88793SEduardo Valentin /* one temperature input for each thermal zone */ 370dd88793SEduardo Valentin struct thermal_hwmon_temp { 380dd88793SEduardo Valentin struct list_head hwmon_node; 390dd88793SEduardo Valentin struct thermal_zone_device *tz; 400dd88793SEduardo Valentin struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ 410dd88793SEduardo Valentin struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ 420dd88793SEduardo Valentin }; 430dd88793SEduardo Valentin 440dd88793SEduardo Valentin static LIST_HEAD(thermal_hwmon_list); 450dd88793SEduardo Valentin 460dd88793SEduardo Valentin static DEFINE_MUTEX(thermal_hwmon_list_lock); 470dd88793SEduardo Valentin 480dd88793SEduardo Valentin static ssize_t 490dd88793SEduardo Valentin temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) 500dd88793SEduardo Valentin { 5117e8351aSSascha Hauer int temperature; 520dd88793SEduardo Valentin int ret; 530dd88793SEduardo Valentin struct thermal_hwmon_attr *hwmon_attr 540dd88793SEduardo Valentin = container_of(attr, struct thermal_hwmon_attr, attr); 550dd88793SEduardo Valentin struct thermal_hwmon_temp *temp 560dd88793SEduardo Valentin = container_of(hwmon_attr, struct thermal_hwmon_temp, 570dd88793SEduardo Valentin temp_input); 580dd88793SEduardo Valentin struct thermal_zone_device *tz = temp->tz; 590dd88793SEduardo Valentin 600dd88793SEduardo Valentin ret = thermal_zone_get_temp(tz, &temperature); 610dd88793SEduardo Valentin 620dd88793SEduardo Valentin if (ret) 630dd88793SEduardo Valentin return ret; 640dd88793SEduardo Valentin 6517e8351aSSascha Hauer return sprintf(buf, "%d\n", temperature); 660dd88793SEduardo Valentin } 670dd88793SEduardo Valentin 680dd88793SEduardo Valentin static ssize_t 690dd88793SEduardo Valentin temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) 700dd88793SEduardo Valentin { 710dd88793SEduardo Valentin struct thermal_hwmon_attr *hwmon_attr 720dd88793SEduardo Valentin = container_of(attr, struct thermal_hwmon_attr, attr); 730dd88793SEduardo Valentin struct thermal_hwmon_temp *temp 740dd88793SEduardo Valentin = container_of(hwmon_attr, struct thermal_hwmon_temp, 750dd88793SEduardo Valentin temp_crit); 760dd88793SEduardo Valentin struct thermal_zone_device *tz = temp->tz; 7717e8351aSSascha Hauer int temperature; 780dd88793SEduardo Valentin int ret; 790dd88793SEduardo Valentin 80f37fabb8SKrzysztof Kozlowski ret = tz->ops->get_crit_temp(tz, &temperature); 810dd88793SEduardo Valentin if (ret) 820dd88793SEduardo Valentin return ret; 830dd88793SEduardo Valentin 8417e8351aSSascha Hauer return sprintf(buf, "%d\n", temperature); 850dd88793SEduardo Valentin } 860dd88793SEduardo Valentin 870dd88793SEduardo Valentin 880dd88793SEduardo Valentin static struct thermal_hwmon_device * 890dd88793SEduardo Valentin thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) 900dd88793SEduardo Valentin { 910dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 928c7aa184SStefan Mavrodiev char type[THERMAL_NAME_LENGTH]; 930dd88793SEduardo Valentin 940dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 958c7aa184SStefan Mavrodiev list_for_each_entry(hwmon, &thermal_hwmon_list, node) { 968c7aa184SStefan Mavrodiev strcpy(type, tz->type); 978c7aa184SStefan Mavrodiev strreplace(type, '-', '_'); 988c7aa184SStefan Mavrodiev if (!strcmp(hwmon->type, type)) { 990dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1000dd88793SEduardo Valentin return hwmon; 1010dd88793SEduardo Valentin } 1028c7aa184SStefan Mavrodiev } 1030dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1040dd88793SEduardo Valentin 1050dd88793SEduardo Valentin return NULL; 1060dd88793SEduardo Valentin } 1070dd88793SEduardo Valentin 1080dd88793SEduardo Valentin /* Find the temperature input matching a given thermal zone */ 1090dd88793SEduardo Valentin static struct thermal_hwmon_temp * 1100dd88793SEduardo Valentin thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, 1110dd88793SEduardo Valentin const struct thermal_zone_device *tz) 1120dd88793SEduardo Valentin { 1130dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 1140dd88793SEduardo Valentin 1150dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 1160dd88793SEduardo Valentin list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) 1170dd88793SEduardo Valentin if (temp->tz == tz) { 1180dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1190dd88793SEduardo Valentin return temp; 1200dd88793SEduardo Valentin } 1210dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1220dd88793SEduardo Valentin 1230dd88793SEduardo Valentin return NULL; 1240dd88793SEduardo Valentin } 1250dd88793SEduardo Valentin 126e8db5d67SAaron Lu static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) 127e8db5d67SAaron Lu { 12817e8351aSSascha Hauer int temp; 129e8db5d67SAaron Lu return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp); 130e8db5d67SAaron Lu } 131e8db5d67SAaron Lu 1320dd88793SEduardo Valentin int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) 1330dd88793SEduardo Valentin { 1340dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 1350dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 1360dd88793SEduardo Valentin int new_hwmon_device = 1; 1370dd88793SEduardo Valentin int result; 1380dd88793SEduardo Valentin 1390dd88793SEduardo Valentin hwmon = thermal_hwmon_lookup_by_type(tz); 1400dd88793SEduardo Valentin if (hwmon) { 1410dd88793SEduardo Valentin new_hwmon_device = 0; 1420dd88793SEduardo Valentin goto register_sys_interface; 1430dd88793SEduardo Valentin } 1440dd88793SEduardo Valentin 1450dd88793SEduardo Valentin hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL); 1460dd88793SEduardo Valentin if (!hwmon) 1470dd88793SEduardo Valentin return -ENOMEM; 1480dd88793SEduardo Valentin 1490dd88793SEduardo Valentin INIT_LIST_HEAD(&hwmon->tz_list); 1500dd88793SEduardo Valentin strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); 151409ef0baSMarc Zyngier strreplace(hwmon->type, '-', '_'); 152f6b6b52eSMarc Zyngier hwmon->device = hwmon_device_register_with_info(&tz->device, hwmon->type, 153e782bc16SFabio Estevam hwmon, NULL, NULL); 1540dd88793SEduardo Valentin if (IS_ERR(hwmon->device)) { 1550dd88793SEduardo Valentin result = PTR_ERR(hwmon->device); 1560dd88793SEduardo Valentin goto free_mem; 1570dd88793SEduardo Valentin } 1580dd88793SEduardo Valentin 1590dd88793SEduardo Valentin register_sys_interface: 1600dd88793SEduardo Valentin temp = kzalloc(sizeof(*temp), GFP_KERNEL); 1610dd88793SEduardo Valentin if (!temp) { 1620dd88793SEduardo Valentin result = -ENOMEM; 1630dd88793SEduardo Valentin goto unregister_name; 1640dd88793SEduardo Valentin } 1650dd88793SEduardo Valentin 1660dd88793SEduardo Valentin temp->tz = tz; 1670dd88793SEduardo Valentin hwmon->count++; 1680dd88793SEduardo Valentin 1690dd88793SEduardo Valentin snprintf(temp->temp_input.name, sizeof(temp->temp_input.name), 1700dd88793SEduardo Valentin "temp%d_input", hwmon->count); 1710dd88793SEduardo Valentin temp->temp_input.attr.attr.name = temp->temp_input.name; 1720dd88793SEduardo Valentin temp->temp_input.attr.attr.mode = 0444; 1730dd88793SEduardo Valentin temp->temp_input.attr.show = temp_input_show; 1740dd88793SEduardo Valentin sysfs_attr_init(&temp->temp_input.attr.attr); 1750dd88793SEduardo Valentin result = device_create_file(hwmon->device, &temp->temp_input.attr); 1760dd88793SEduardo Valentin if (result) 1770dd88793SEduardo Valentin goto free_temp_mem; 1780dd88793SEduardo Valentin 179e8db5d67SAaron Lu if (thermal_zone_crit_temp_valid(tz)) { 1800dd88793SEduardo Valentin snprintf(temp->temp_crit.name, 1810dd88793SEduardo Valentin sizeof(temp->temp_crit.name), 1820dd88793SEduardo Valentin "temp%d_crit", hwmon->count); 1830dd88793SEduardo Valentin temp->temp_crit.attr.attr.name = temp->temp_crit.name; 1840dd88793SEduardo Valentin temp->temp_crit.attr.attr.mode = 0444; 1850dd88793SEduardo Valentin temp->temp_crit.attr.show = temp_crit_show; 1860dd88793SEduardo Valentin sysfs_attr_init(&temp->temp_crit.attr.attr); 1870dd88793SEduardo Valentin result = device_create_file(hwmon->device, 1880dd88793SEduardo Valentin &temp->temp_crit.attr); 1890dd88793SEduardo Valentin if (result) 1900dd88793SEduardo Valentin goto unregister_input; 1910dd88793SEduardo Valentin } 1920dd88793SEduardo Valentin 1930dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 1940dd88793SEduardo Valentin if (new_hwmon_device) 1950dd88793SEduardo Valentin list_add_tail(&hwmon->node, &thermal_hwmon_list); 1960dd88793SEduardo Valentin list_add_tail(&temp->hwmon_node, &hwmon->tz_list); 1970dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1980dd88793SEduardo Valentin 1990dd88793SEduardo Valentin return 0; 2000dd88793SEduardo Valentin 2010dd88793SEduardo Valentin unregister_input: 2020dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_input.attr); 2030dd88793SEduardo Valentin free_temp_mem: 2040dd88793SEduardo Valentin kfree(temp); 2050dd88793SEduardo Valentin unregister_name: 206e782bc16SFabio Estevam if (new_hwmon_device) 2070dd88793SEduardo Valentin hwmon_device_unregister(hwmon->device); 2080dd88793SEduardo Valentin free_mem: 2090dd88793SEduardo Valentin if (new_hwmon_device) 2100dd88793SEduardo Valentin kfree(hwmon); 2110dd88793SEduardo Valentin 2120dd88793SEduardo Valentin return result; 2130dd88793SEduardo Valentin } 214f4c59243SKuninori Morimoto EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); 2150dd88793SEduardo Valentin 2160dd88793SEduardo Valentin void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) 2170dd88793SEduardo Valentin { 2180dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 2190dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 2200dd88793SEduardo Valentin 2210dd88793SEduardo Valentin hwmon = thermal_hwmon_lookup_by_type(tz); 2220dd88793SEduardo Valentin if (unlikely(!hwmon)) { 2230dd88793SEduardo Valentin /* Should never happen... */ 2240dd88793SEduardo Valentin dev_dbg(&tz->device, "hwmon device lookup failed!\n"); 2250dd88793SEduardo Valentin return; 2260dd88793SEduardo Valentin } 2270dd88793SEduardo Valentin 2280dd88793SEduardo Valentin temp = thermal_hwmon_lookup_temp(hwmon, tz); 2290dd88793SEduardo Valentin if (unlikely(!temp)) { 2300dd88793SEduardo Valentin /* Should never happen... */ 2310dd88793SEduardo Valentin dev_dbg(&tz->device, "temperature input lookup failed!\n"); 2320dd88793SEduardo Valentin return; 2330dd88793SEduardo Valentin } 2340dd88793SEduardo Valentin 2350dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_input.attr); 236e8db5d67SAaron Lu if (thermal_zone_crit_temp_valid(tz)) 2370dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_crit.attr); 2380dd88793SEduardo Valentin 2390dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 2400dd88793SEduardo Valentin list_del(&temp->hwmon_node); 2410dd88793SEduardo Valentin kfree(temp); 2420dd88793SEduardo Valentin if (!list_empty(&hwmon->tz_list)) { 2430dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 2440dd88793SEduardo Valentin return; 2450dd88793SEduardo Valentin } 2460dd88793SEduardo Valentin list_del(&hwmon->node); 2470dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 2480dd88793SEduardo Valentin 2490dd88793SEduardo Valentin hwmon_device_unregister(hwmon->device); 2500dd88793SEduardo Valentin kfree(hwmon); 2510dd88793SEduardo Valentin } 252f4c59243SKuninori Morimoto EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs); 253c7fc403eSAndrey Smirnov 254c7fc403eSAndrey Smirnov static void devm_thermal_hwmon_release(struct device *dev, void *res) 255c7fc403eSAndrey Smirnov { 256c7fc403eSAndrey Smirnov thermal_remove_hwmon_sysfs(*(struct thermal_zone_device **)res); 257c7fc403eSAndrey Smirnov } 258c7fc403eSAndrey Smirnov 259c7fc403eSAndrey Smirnov int devm_thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) 260c7fc403eSAndrey Smirnov { 261c7fc403eSAndrey Smirnov struct thermal_zone_device **ptr; 262c7fc403eSAndrey Smirnov int ret; 263c7fc403eSAndrey Smirnov 264c7fc403eSAndrey Smirnov ptr = devres_alloc(devm_thermal_hwmon_release, sizeof(*ptr), 265c7fc403eSAndrey Smirnov GFP_KERNEL); 266c7fc403eSAndrey Smirnov if (!ptr) 267c7fc403eSAndrey Smirnov return -ENOMEM; 268c7fc403eSAndrey Smirnov 269c7fc403eSAndrey Smirnov ret = thermal_add_hwmon_sysfs(tz); 270c7fc403eSAndrey Smirnov if (ret) { 271c7fc403eSAndrey Smirnov devres_free(ptr); 272c7fc403eSAndrey Smirnov return ret; 273c7fc403eSAndrey Smirnov } 274c7fc403eSAndrey Smirnov 275c7fc403eSAndrey Smirnov *ptr = tz; 276c7fc403eSAndrey Smirnov devres_add(&tz->device, ptr); 277c7fc403eSAndrey Smirnov 278c7fc403eSAndrey Smirnov return ret; 279c7fc403eSAndrey Smirnov } 280c7fc403eSAndrey Smirnov EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs); 281