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> 14e5ebf357SAmit 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 80ea37bec5SGuenter Roeck mutex_lock(&tz->lock); 81ea37bec5SGuenter Roeck 82ea37bec5SGuenter Roeck if (device_is_registered(&tz->device)) 83f37fabb8SKrzysztof Kozlowski ret = tz->ops->get_crit_temp(tz, &temperature); 84ea37bec5SGuenter Roeck else 85ea37bec5SGuenter Roeck ret = -ENODEV; 86ea37bec5SGuenter Roeck 87ea37bec5SGuenter Roeck mutex_unlock(&tz->lock); 88ea37bec5SGuenter Roeck 890dd88793SEduardo Valentin if (ret) 900dd88793SEduardo Valentin return ret; 910dd88793SEduardo Valentin 9217e8351aSSascha Hauer return sprintf(buf, "%d\n", temperature); 930dd88793SEduardo Valentin } 940dd88793SEduardo Valentin 950dd88793SEduardo Valentin 960dd88793SEduardo Valentin static struct thermal_hwmon_device * 970dd88793SEduardo Valentin thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) 980dd88793SEduardo Valentin { 990dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 1008c7aa184SStefan Mavrodiev char type[THERMAL_NAME_LENGTH]; 1010dd88793SEduardo Valentin 1020dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 1038c7aa184SStefan Mavrodiev list_for_each_entry(hwmon, &thermal_hwmon_list, node) { 1048c7aa184SStefan Mavrodiev strcpy(type, tz->type); 1058c7aa184SStefan Mavrodiev strreplace(type, '-', '_'); 1068c7aa184SStefan Mavrodiev if (!strcmp(hwmon->type, type)) { 1070dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1080dd88793SEduardo Valentin return hwmon; 1090dd88793SEduardo Valentin } 1108c7aa184SStefan Mavrodiev } 1110dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1120dd88793SEduardo Valentin 1130dd88793SEduardo Valentin return NULL; 1140dd88793SEduardo Valentin } 1150dd88793SEduardo Valentin 1160dd88793SEduardo Valentin /* Find the temperature input matching a given thermal zone */ 1170dd88793SEduardo Valentin static struct thermal_hwmon_temp * 1180dd88793SEduardo Valentin thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, 1190dd88793SEduardo Valentin const struct thermal_zone_device *tz) 1200dd88793SEduardo Valentin { 1210dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 1220dd88793SEduardo Valentin 1230dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 1240dd88793SEduardo Valentin list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) 1250dd88793SEduardo Valentin if (temp->tz == tz) { 1260dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1270dd88793SEduardo Valentin return temp; 1280dd88793SEduardo Valentin } 1290dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1300dd88793SEduardo Valentin 1310dd88793SEduardo Valentin return NULL; 1320dd88793SEduardo Valentin } 1330dd88793SEduardo Valentin 134e8db5d67SAaron Lu static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) 135e8db5d67SAaron Lu { 13617e8351aSSascha Hauer int temp; 137e8db5d67SAaron Lu return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp); 138e8db5d67SAaron Lu } 139e8db5d67SAaron Lu 1400dd88793SEduardo Valentin int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) 1410dd88793SEduardo Valentin { 1420dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 1430dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 1440dd88793SEduardo Valentin int new_hwmon_device = 1; 1450dd88793SEduardo Valentin int result; 1460dd88793SEduardo Valentin 1470dd88793SEduardo Valentin hwmon = thermal_hwmon_lookup_by_type(tz); 1480dd88793SEduardo Valentin if (hwmon) { 1490dd88793SEduardo Valentin new_hwmon_device = 0; 1500dd88793SEduardo Valentin goto register_sys_interface; 1510dd88793SEduardo Valentin } 1520dd88793SEduardo Valentin 1530dd88793SEduardo Valentin hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL); 1540dd88793SEduardo Valentin if (!hwmon) 1550dd88793SEduardo Valentin return -ENOMEM; 1560dd88793SEduardo Valentin 1570dd88793SEduardo Valentin INIT_LIST_HEAD(&hwmon->tz_list); 1581e6c8fb8SWolfram Sang strscpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); 159409ef0baSMarc Zyngier strreplace(hwmon->type, '-', '_'); 16087743bcfSGuenter Roeck hwmon->device = hwmon_device_register_for_thermal(&tz->device, 16187743bcfSGuenter Roeck hwmon->type, hwmon); 1620dd88793SEduardo Valentin if (IS_ERR(hwmon->device)) { 1630dd88793SEduardo Valentin result = PTR_ERR(hwmon->device); 1640dd88793SEduardo Valentin goto free_mem; 1650dd88793SEduardo Valentin } 1660dd88793SEduardo Valentin 1670dd88793SEduardo Valentin register_sys_interface: 1680dd88793SEduardo Valentin temp = kzalloc(sizeof(*temp), GFP_KERNEL); 1690dd88793SEduardo Valentin if (!temp) { 1700dd88793SEduardo Valentin result = -ENOMEM; 1710dd88793SEduardo Valentin goto unregister_name; 1720dd88793SEduardo Valentin } 1730dd88793SEduardo Valentin 1740dd88793SEduardo Valentin temp->tz = tz; 1750dd88793SEduardo Valentin hwmon->count++; 1760dd88793SEduardo Valentin 1770dd88793SEduardo Valentin snprintf(temp->temp_input.name, sizeof(temp->temp_input.name), 1780dd88793SEduardo Valentin "temp%d_input", hwmon->count); 1790dd88793SEduardo Valentin temp->temp_input.attr.attr.name = temp->temp_input.name; 1800dd88793SEduardo Valentin temp->temp_input.attr.attr.mode = 0444; 1810dd88793SEduardo Valentin temp->temp_input.attr.show = temp_input_show; 1820dd88793SEduardo Valentin sysfs_attr_init(&temp->temp_input.attr.attr); 1830dd88793SEduardo Valentin result = device_create_file(hwmon->device, &temp->temp_input.attr); 1840dd88793SEduardo Valentin if (result) 1850dd88793SEduardo Valentin goto free_temp_mem; 1860dd88793SEduardo Valentin 187e8db5d67SAaron Lu if (thermal_zone_crit_temp_valid(tz)) { 1880dd88793SEduardo Valentin snprintf(temp->temp_crit.name, 1890dd88793SEduardo Valentin sizeof(temp->temp_crit.name), 1900dd88793SEduardo Valentin "temp%d_crit", hwmon->count); 1910dd88793SEduardo Valentin temp->temp_crit.attr.attr.name = temp->temp_crit.name; 1920dd88793SEduardo Valentin temp->temp_crit.attr.attr.mode = 0444; 1930dd88793SEduardo Valentin temp->temp_crit.attr.show = temp_crit_show; 1940dd88793SEduardo Valentin sysfs_attr_init(&temp->temp_crit.attr.attr); 1950dd88793SEduardo Valentin result = device_create_file(hwmon->device, 1960dd88793SEduardo Valentin &temp->temp_crit.attr); 1970dd88793SEduardo Valentin if (result) 1980dd88793SEduardo Valentin goto unregister_input; 1990dd88793SEduardo Valentin } 2000dd88793SEduardo Valentin 2010dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 2020dd88793SEduardo Valentin if (new_hwmon_device) 2030dd88793SEduardo Valentin list_add_tail(&hwmon->node, &thermal_hwmon_list); 2040dd88793SEduardo Valentin list_add_tail(&temp->hwmon_node, &hwmon->tz_list); 2050dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 2060dd88793SEduardo Valentin 2070dd88793SEduardo Valentin return 0; 2080dd88793SEduardo Valentin 2090dd88793SEduardo Valentin unregister_input: 2100dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_input.attr); 2110dd88793SEduardo Valentin free_temp_mem: 2120dd88793SEduardo Valentin kfree(temp); 2130dd88793SEduardo Valentin unregister_name: 214e782bc16SFabio Estevam if (new_hwmon_device) 2150dd88793SEduardo Valentin hwmon_device_unregister(hwmon->device); 2160dd88793SEduardo Valentin free_mem: 2170dd88793SEduardo Valentin kfree(hwmon); 2180dd88793SEduardo Valentin 2190dd88793SEduardo Valentin return result; 2200dd88793SEduardo Valentin } 221f4c59243SKuninori Morimoto EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); 2220dd88793SEduardo Valentin 2230dd88793SEduardo Valentin void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) 2240dd88793SEduardo Valentin { 2250dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 2260dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 2270dd88793SEduardo Valentin 2280dd88793SEduardo Valentin hwmon = thermal_hwmon_lookup_by_type(tz); 2290dd88793SEduardo Valentin if (unlikely(!hwmon)) { 2300dd88793SEduardo Valentin /* Should never happen... */ 2310dd88793SEduardo Valentin dev_dbg(&tz->device, "hwmon device lookup failed!\n"); 2320dd88793SEduardo Valentin return; 2330dd88793SEduardo Valentin } 2340dd88793SEduardo Valentin 2350dd88793SEduardo Valentin temp = thermal_hwmon_lookup_temp(hwmon, tz); 2360dd88793SEduardo Valentin if (unlikely(!temp)) { 2370dd88793SEduardo Valentin /* Should never happen... */ 2380dd88793SEduardo Valentin dev_dbg(&tz->device, "temperature input lookup failed!\n"); 2390dd88793SEduardo Valentin return; 2400dd88793SEduardo Valentin } 2410dd88793SEduardo Valentin 2420dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_input.attr); 243e8db5d67SAaron Lu if (thermal_zone_crit_temp_valid(tz)) 2440dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_crit.attr); 2450dd88793SEduardo Valentin 2460dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 2470dd88793SEduardo Valentin list_del(&temp->hwmon_node); 2480dd88793SEduardo Valentin kfree(temp); 2490dd88793SEduardo Valentin if (!list_empty(&hwmon->tz_list)) { 2500dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 2510dd88793SEduardo Valentin return; 2520dd88793SEduardo Valentin } 2530dd88793SEduardo Valentin list_del(&hwmon->node); 2540dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 2550dd88793SEduardo Valentin 2560dd88793SEduardo Valentin hwmon_device_unregister(hwmon->device); 2570dd88793SEduardo Valentin kfree(hwmon); 2580dd88793SEduardo Valentin } 259f4c59243SKuninori Morimoto EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs); 260c7fc403eSAndrey Smirnov 261c7fc403eSAndrey Smirnov static void devm_thermal_hwmon_release(struct device *dev, void *res) 262c7fc403eSAndrey Smirnov { 263c7fc403eSAndrey Smirnov thermal_remove_hwmon_sysfs(*(struct thermal_zone_device **)res); 264c7fc403eSAndrey Smirnov } 265c7fc403eSAndrey Smirnov 266*4a16c190SDaniel Lezcano int devm_thermal_add_hwmon_sysfs(struct device *dev, struct thermal_zone_device *tz) 267c7fc403eSAndrey Smirnov { 268c7fc403eSAndrey Smirnov struct thermal_zone_device **ptr; 269c7fc403eSAndrey Smirnov int ret; 270c7fc403eSAndrey Smirnov 271c7fc403eSAndrey Smirnov ptr = devres_alloc(devm_thermal_hwmon_release, sizeof(*ptr), 272c7fc403eSAndrey Smirnov GFP_KERNEL); 273c7fc403eSAndrey Smirnov if (!ptr) 274c7fc403eSAndrey Smirnov return -ENOMEM; 275c7fc403eSAndrey Smirnov 276c7fc403eSAndrey Smirnov ret = thermal_add_hwmon_sysfs(tz); 277c7fc403eSAndrey Smirnov if (ret) { 278c7fc403eSAndrey Smirnov devres_free(ptr); 279c7fc403eSAndrey Smirnov return ret; 280c7fc403eSAndrey Smirnov } 281c7fc403eSAndrey Smirnov 282c7fc403eSAndrey Smirnov *ptr = tz; 283*4a16c190SDaniel Lezcano devres_add(dev, ptr); 284c7fc403eSAndrey Smirnov 285c7fc403eSAndrey Smirnov return ret; 286c7fc403eSAndrey Smirnov } 287c7fc403eSAndrey Smirnov EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs); 28887743bcfSGuenter Roeck 28987743bcfSGuenter Roeck MODULE_IMPORT_NS(HWMON_THERMAL); 290