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" 20ec2c8aaeSDaniel Lezcano #include "thermal_core.h" 210dd88793SEduardo Valentin 220dd88793SEduardo Valentin /* hwmon sys I/F */ 230dd88793SEduardo Valentin /* thermal zone devices with the same type share one hwmon device */ 240dd88793SEduardo Valentin struct thermal_hwmon_device { 250dd88793SEduardo Valentin char type[THERMAL_NAME_LENGTH]; 260dd88793SEduardo Valentin struct device *device; 270dd88793SEduardo Valentin int count; 280dd88793SEduardo Valentin struct list_head tz_list; 290dd88793SEduardo Valentin struct list_head node; 300dd88793SEduardo Valentin }; 310dd88793SEduardo Valentin 320dd88793SEduardo Valentin struct thermal_hwmon_attr { 330dd88793SEduardo Valentin struct device_attribute attr; 340dd88793SEduardo Valentin char name[16]; 350dd88793SEduardo Valentin }; 360dd88793SEduardo Valentin 370dd88793SEduardo Valentin /* one temperature input for each thermal zone */ 380dd88793SEduardo Valentin struct thermal_hwmon_temp { 390dd88793SEduardo Valentin struct list_head hwmon_node; 400dd88793SEduardo Valentin struct thermal_zone_device *tz; 410dd88793SEduardo Valentin struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ 420dd88793SEduardo Valentin struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ 430dd88793SEduardo Valentin }; 440dd88793SEduardo Valentin 450dd88793SEduardo Valentin static LIST_HEAD(thermal_hwmon_list); 460dd88793SEduardo Valentin 470dd88793SEduardo Valentin static DEFINE_MUTEX(thermal_hwmon_list_lock); 480dd88793SEduardo Valentin 490dd88793SEduardo Valentin static ssize_t 500dd88793SEduardo Valentin temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) 510dd88793SEduardo Valentin { 5217e8351aSSascha Hauer int temperature; 530dd88793SEduardo Valentin int ret; 540dd88793SEduardo Valentin struct thermal_hwmon_attr *hwmon_attr 550dd88793SEduardo Valentin = container_of(attr, struct thermal_hwmon_attr, attr); 560dd88793SEduardo Valentin struct thermal_hwmon_temp *temp 570dd88793SEduardo Valentin = container_of(hwmon_attr, struct thermal_hwmon_temp, 580dd88793SEduardo Valentin temp_input); 590dd88793SEduardo Valentin struct thermal_zone_device *tz = temp->tz; 600dd88793SEduardo Valentin 610dd88793SEduardo Valentin ret = thermal_zone_get_temp(tz, &temperature); 620dd88793SEduardo Valentin 630dd88793SEduardo Valentin if (ret) 640dd88793SEduardo Valentin return ret; 650dd88793SEduardo Valentin 6617e8351aSSascha Hauer return sprintf(buf, "%d\n", temperature); 670dd88793SEduardo Valentin } 680dd88793SEduardo Valentin 690dd88793SEduardo Valentin static ssize_t 700dd88793SEduardo Valentin temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) 710dd88793SEduardo Valentin { 720dd88793SEduardo Valentin struct thermal_hwmon_attr *hwmon_attr 730dd88793SEduardo Valentin = container_of(attr, struct thermal_hwmon_attr, attr); 740dd88793SEduardo Valentin struct thermal_hwmon_temp *temp 750dd88793SEduardo Valentin = container_of(hwmon_attr, struct thermal_hwmon_temp, 760dd88793SEduardo Valentin temp_crit); 770dd88793SEduardo Valentin struct thermal_zone_device *tz = temp->tz; 7817e8351aSSascha Hauer int temperature; 790dd88793SEduardo Valentin int ret; 800dd88793SEduardo Valentin 81ea37bec5SGuenter Roeck mutex_lock(&tz->lock); 82ea37bec5SGuenter Roeck 83ea37bec5SGuenter Roeck if (device_is_registered(&tz->device)) 84f37fabb8SKrzysztof Kozlowski ret = tz->ops->get_crit_temp(tz, &temperature); 85ea37bec5SGuenter Roeck else 86ea37bec5SGuenter Roeck ret = -ENODEV; 87ea37bec5SGuenter Roeck 88ea37bec5SGuenter Roeck mutex_unlock(&tz->lock); 89ea37bec5SGuenter Roeck 900dd88793SEduardo Valentin if (ret) 910dd88793SEduardo Valentin return ret; 920dd88793SEduardo Valentin 9317e8351aSSascha Hauer return sprintf(buf, "%d\n", temperature); 940dd88793SEduardo Valentin } 950dd88793SEduardo Valentin 960dd88793SEduardo Valentin 970dd88793SEduardo Valentin static struct thermal_hwmon_device * 980dd88793SEduardo Valentin thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) 990dd88793SEduardo Valentin { 1000dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 1018c7aa184SStefan Mavrodiev char type[THERMAL_NAME_LENGTH]; 1020dd88793SEduardo Valentin 1030dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 1048c7aa184SStefan Mavrodiev list_for_each_entry(hwmon, &thermal_hwmon_list, node) { 1058c7aa184SStefan Mavrodiev strcpy(type, tz->type); 1068c7aa184SStefan Mavrodiev strreplace(type, '-', '_'); 1078c7aa184SStefan Mavrodiev if (!strcmp(hwmon->type, type)) { 1080dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1090dd88793SEduardo Valentin return hwmon; 1100dd88793SEduardo Valentin } 1118c7aa184SStefan Mavrodiev } 1120dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1130dd88793SEduardo Valentin 1140dd88793SEduardo Valentin return NULL; 1150dd88793SEduardo Valentin } 1160dd88793SEduardo Valentin 1170dd88793SEduardo Valentin /* Find the temperature input matching a given thermal zone */ 1180dd88793SEduardo Valentin static struct thermal_hwmon_temp * 1190dd88793SEduardo Valentin thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, 1200dd88793SEduardo Valentin const struct thermal_zone_device *tz) 1210dd88793SEduardo Valentin { 1220dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 1230dd88793SEduardo Valentin 1240dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 1250dd88793SEduardo Valentin list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) 1260dd88793SEduardo Valentin if (temp->tz == tz) { 1270dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1280dd88793SEduardo Valentin return temp; 1290dd88793SEduardo Valentin } 1300dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1310dd88793SEduardo Valentin 1320dd88793SEduardo Valentin return NULL; 1330dd88793SEduardo Valentin } 1340dd88793SEduardo Valentin 135e8db5d67SAaron Lu static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) 136e8db5d67SAaron Lu { 13717e8351aSSascha Hauer int temp; 138e8db5d67SAaron Lu return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp); 139e8db5d67SAaron Lu } 140e8db5d67SAaron Lu 1410dd88793SEduardo Valentin int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) 1420dd88793SEduardo Valentin { 1430dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 1440dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 1450dd88793SEduardo Valentin int new_hwmon_device = 1; 1460dd88793SEduardo Valentin int result; 1470dd88793SEduardo Valentin 1480dd88793SEduardo Valentin hwmon = thermal_hwmon_lookup_by_type(tz); 1490dd88793SEduardo Valentin if (hwmon) { 1500dd88793SEduardo Valentin new_hwmon_device = 0; 1510dd88793SEduardo Valentin goto register_sys_interface; 1520dd88793SEduardo Valentin } 1530dd88793SEduardo Valentin 1540dd88793SEduardo Valentin hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL); 1550dd88793SEduardo Valentin if (!hwmon) 1560dd88793SEduardo Valentin return -ENOMEM; 1570dd88793SEduardo Valentin 1580dd88793SEduardo Valentin INIT_LIST_HEAD(&hwmon->tz_list); 1591e6c8fb8SWolfram Sang strscpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); 160409ef0baSMarc Zyngier strreplace(hwmon->type, '-', '_'); 16187743bcfSGuenter Roeck hwmon->device = hwmon_device_register_for_thermal(&tz->device, 16287743bcfSGuenter Roeck hwmon->type, hwmon); 1630dd88793SEduardo Valentin if (IS_ERR(hwmon->device)) { 1640dd88793SEduardo Valentin result = PTR_ERR(hwmon->device); 1650dd88793SEduardo Valentin goto free_mem; 1660dd88793SEduardo Valentin } 1670dd88793SEduardo Valentin 1680dd88793SEduardo Valentin register_sys_interface: 1690dd88793SEduardo Valentin temp = kzalloc(sizeof(*temp), GFP_KERNEL); 1700dd88793SEduardo Valentin if (!temp) { 1710dd88793SEduardo Valentin result = -ENOMEM; 1720dd88793SEduardo Valentin goto unregister_name; 1730dd88793SEduardo Valentin } 1740dd88793SEduardo Valentin 1750dd88793SEduardo Valentin temp->tz = tz; 1760dd88793SEduardo Valentin hwmon->count++; 1770dd88793SEduardo Valentin 1780dd88793SEduardo Valentin snprintf(temp->temp_input.name, sizeof(temp->temp_input.name), 1790dd88793SEduardo Valentin "temp%d_input", hwmon->count); 1800dd88793SEduardo Valentin temp->temp_input.attr.attr.name = temp->temp_input.name; 1810dd88793SEduardo Valentin temp->temp_input.attr.attr.mode = 0444; 1820dd88793SEduardo Valentin temp->temp_input.attr.show = temp_input_show; 1830dd88793SEduardo Valentin sysfs_attr_init(&temp->temp_input.attr.attr); 1840dd88793SEduardo Valentin result = device_create_file(hwmon->device, &temp->temp_input.attr); 1850dd88793SEduardo Valentin if (result) 1860dd88793SEduardo Valentin goto free_temp_mem; 1870dd88793SEduardo Valentin 188e8db5d67SAaron Lu if (thermal_zone_crit_temp_valid(tz)) { 1890dd88793SEduardo Valentin snprintf(temp->temp_crit.name, 1900dd88793SEduardo Valentin sizeof(temp->temp_crit.name), 1910dd88793SEduardo Valentin "temp%d_crit", hwmon->count); 1920dd88793SEduardo Valentin temp->temp_crit.attr.attr.name = temp->temp_crit.name; 1930dd88793SEduardo Valentin temp->temp_crit.attr.attr.mode = 0444; 1940dd88793SEduardo Valentin temp->temp_crit.attr.show = temp_crit_show; 1950dd88793SEduardo Valentin sysfs_attr_init(&temp->temp_crit.attr.attr); 1960dd88793SEduardo Valentin result = device_create_file(hwmon->device, 1970dd88793SEduardo Valentin &temp->temp_crit.attr); 1980dd88793SEduardo Valentin if (result) 1990dd88793SEduardo Valentin goto unregister_input; 2000dd88793SEduardo Valentin } 2010dd88793SEduardo Valentin 2020dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 2030dd88793SEduardo Valentin if (new_hwmon_device) 2040dd88793SEduardo Valentin list_add_tail(&hwmon->node, &thermal_hwmon_list); 2050dd88793SEduardo Valentin list_add_tail(&temp->hwmon_node, &hwmon->tz_list); 2060dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 2070dd88793SEduardo Valentin 2080dd88793SEduardo Valentin return 0; 2090dd88793SEduardo Valentin 2100dd88793SEduardo Valentin unregister_input: 2110dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_input.attr); 2120dd88793SEduardo Valentin free_temp_mem: 2130dd88793SEduardo Valentin kfree(temp); 2140dd88793SEduardo Valentin unregister_name: 215e782bc16SFabio Estevam if (new_hwmon_device) 2160dd88793SEduardo Valentin hwmon_device_unregister(hwmon->device); 2170dd88793SEduardo Valentin free_mem: 2180dd88793SEduardo Valentin kfree(hwmon); 2190dd88793SEduardo Valentin 2200dd88793SEduardo Valentin return result; 2210dd88793SEduardo Valentin } 222f4c59243SKuninori Morimoto EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); 2230dd88793SEduardo Valentin 2240dd88793SEduardo Valentin void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) 2250dd88793SEduardo Valentin { 2260dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 2270dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 2280dd88793SEduardo Valentin 2290dd88793SEduardo Valentin hwmon = thermal_hwmon_lookup_by_type(tz); 2300dd88793SEduardo Valentin if (unlikely(!hwmon)) { 2310dd88793SEduardo Valentin /* Should never happen... */ 2324eb7c2f3SZhang Rui dev_dbg(&tz->device, "hwmon device lookup failed!\n"); 2330dd88793SEduardo Valentin return; 2340dd88793SEduardo Valentin } 2350dd88793SEduardo Valentin 2360dd88793SEduardo Valentin temp = thermal_hwmon_lookup_temp(hwmon, tz); 2370dd88793SEduardo Valentin if (unlikely(!temp)) { 2380dd88793SEduardo Valentin /* Should never happen... */ 239d9dc0600SRafael J. Wysocki dev_dbg(&tz->device, "temperature input lookup failed!\n"); 2400dd88793SEduardo Valentin return; 2410dd88793SEduardo Valentin } 2420dd88793SEduardo Valentin 2430dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_input.attr); 244e8db5d67SAaron Lu if (thermal_zone_crit_temp_valid(tz)) 2450dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_crit.attr); 2460dd88793SEduardo Valentin 2470dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 2480dd88793SEduardo Valentin list_del(&temp->hwmon_node); 2490dd88793SEduardo Valentin kfree(temp); 2500dd88793SEduardo Valentin if (!list_empty(&hwmon->tz_list)) { 2510dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 2520dd88793SEduardo Valentin return; 2530dd88793SEduardo Valentin } 2540dd88793SEduardo Valentin list_del(&hwmon->node); 2550dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 2560dd88793SEduardo Valentin 2570dd88793SEduardo Valentin hwmon_device_unregister(hwmon->device); 2580dd88793SEduardo Valentin kfree(hwmon); 2590dd88793SEduardo Valentin } 260f4c59243SKuninori Morimoto EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs); 261c7fc403eSAndrey Smirnov 262c7fc403eSAndrey Smirnov static void devm_thermal_hwmon_release(struct device *dev, void *res) 263c7fc403eSAndrey Smirnov { 264c7fc403eSAndrey Smirnov thermal_remove_hwmon_sysfs(*(struct thermal_zone_device **)res); 265c7fc403eSAndrey Smirnov } 266c7fc403eSAndrey Smirnov 2674a16c190SDaniel Lezcano int devm_thermal_add_hwmon_sysfs(struct device *dev, struct thermal_zone_device *tz) 268c7fc403eSAndrey Smirnov { 269c7fc403eSAndrey Smirnov struct thermal_zone_device **ptr; 270c7fc403eSAndrey Smirnov int ret; 271c7fc403eSAndrey Smirnov 272c7fc403eSAndrey Smirnov ptr = devres_alloc(devm_thermal_hwmon_release, sizeof(*ptr), 273c7fc403eSAndrey Smirnov GFP_KERNEL); 274*8416ecfbSYangtao Li if (!ptr) { 275*8416ecfbSYangtao Li dev_warn(dev, "Failed to allocate device resource data\n"); 276c7fc403eSAndrey Smirnov return -ENOMEM; 277*8416ecfbSYangtao Li } 278c7fc403eSAndrey Smirnov 279c7fc403eSAndrey Smirnov ret = thermal_add_hwmon_sysfs(tz); 280c7fc403eSAndrey Smirnov if (ret) { 281*8416ecfbSYangtao Li dev_warn(dev, "Failed to add hwmon sysfs attributes\n"); 282c7fc403eSAndrey Smirnov devres_free(ptr); 283c7fc403eSAndrey Smirnov return ret; 284c7fc403eSAndrey Smirnov } 285c7fc403eSAndrey Smirnov 286c7fc403eSAndrey Smirnov *ptr = tz; 2874a16c190SDaniel Lezcano devres_add(dev, ptr); 288c7fc403eSAndrey Smirnov 289c7fc403eSAndrey Smirnov return ret; 290c7fc403eSAndrey Smirnov } 291c7fc403eSAndrey Smirnov EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs); 29287743bcfSGuenter Roeck 29387743bcfSGuenter Roeck MODULE_IMPORT_NS(HWMON_THERMAL); 294