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/hwmon.h> 140dd88793SEduardo Valentin #include <linux/thermal.h> 150dd88793SEduardo Valentin #include <linux/slab.h> 160dd88793SEduardo Valentin #include <linux/err.h> 170dd88793SEduardo Valentin #include "thermal_hwmon.h" 180dd88793SEduardo Valentin 190dd88793SEduardo Valentin /* hwmon sys I/F */ 200dd88793SEduardo Valentin /* thermal zone devices with the same type share one hwmon device */ 210dd88793SEduardo Valentin struct thermal_hwmon_device { 220dd88793SEduardo Valentin char type[THERMAL_NAME_LENGTH]; 230dd88793SEduardo Valentin struct device *device; 240dd88793SEduardo Valentin int count; 250dd88793SEduardo Valentin struct list_head tz_list; 260dd88793SEduardo Valentin struct list_head node; 270dd88793SEduardo Valentin }; 280dd88793SEduardo Valentin 290dd88793SEduardo Valentin struct thermal_hwmon_attr { 300dd88793SEduardo Valentin struct device_attribute attr; 310dd88793SEduardo Valentin char name[16]; 320dd88793SEduardo Valentin }; 330dd88793SEduardo Valentin 340dd88793SEduardo Valentin /* one temperature input for each thermal zone */ 350dd88793SEduardo Valentin struct thermal_hwmon_temp { 360dd88793SEduardo Valentin struct list_head hwmon_node; 370dd88793SEduardo Valentin struct thermal_zone_device *tz; 380dd88793SEduardo Valentin struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ 390dd88793SEduardo Valentin struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ 400dd88793SEduardo Valentin }; 410dd88793SEduardo Valentin 420dd88793SEduardo Valentin static LIST_HEAD(thermal_hwmon_list); 430dd88793SEduardo Valentin 440dd88793SEduardo Valentin static DEFINE_MUTEX(thermal_hwmon_list_lock); 450dd88793SEduardo Valentin 460dd88793SEduardo Valentin static ssize_t 470dd88793SEduardo Valentin temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) 480dd88793SEduardo Valentin { 4917e8351aSSascha Hauer int temperature; 500dd88793SEduardo Valentin int ret; 510dd88793SEduardo Valentin struct thermal_hwmon_attr *hwmon_attr 520dd88793SEduardo Valentin = container_of(attr, struct thermal_hwmon_attr, attr); 530dd88793SEduardo Valentin struct thermal_hwmon_temp *temp 540dd88793SEduardo Valentin = container_of(hwmon_attr, struct thermal_hwmon_temp, 550dd88793SEduardo Valentin temp_input); 560dd88793SEduardo Valentin struct thermal_zone_device *tz = temp->tz; 570dd88793SEduardo Valentin 580dd88793SEduardo Valentin ret = thermal_zone_get_temp(tz, &temperature); 590dd88793SEduardo Valentin 600dd88793SEduardo Valentin if (ret) 610dd88793SEduardo Valentin return ret; 620dd88793SEduardo Valentin 6317e8351aSSascha Hauer return sprintf(buf, "%d\n", temperature); 640dd88793SEduardo Valentin } 650dd88793SEduardo Valentin 660dd88793SEduardo Valentin static ssize_t 670dd88793SEduardo Valentin temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) 680dd88793SEduardo Valentin { 690dd88793SEduardo Valentin struct thermal_hwmon_attr *hwmon_attr 700dd88793SEduardo Valentin = container_of(attr, struct thermal_hwmon_attr, attr); 710dd88793SEduardo Valentin struct thermal_hwmon_temp *temp 720dd88793SEduardo Valentin = container_of(hwmon_attr, struct thermal_hwmon_temp, 730dd88793SEduardo Valentin temp_crit); 740dd88793SEduardo Valentin struct thermal_zone_device *tz = temp->tz; 7517e8351aSSascha Hauer int temperature; 760dd88793SEduardo Valentin int ret; 770dd88793SEduardo Valentin 78f37fabb8SKrzysztof Kozlowski ret = tz->ops->get_crit_temp(tz, &temperature); 790dd88793SEduardo Valentin if (ret) 800dd88793SEduardo Valentin return ret; 810dd88793SEduardo Valentin 8217e8351aSSascha Hauer return sprintf(buf, "%d\n", temperature); 830dd88793SEduardo Valentin } 840dd88793SEduardo Valentin 850dd88793SEduardo Valentin 860dd88793SEduardo Valentin static struct thermal_hwmon_device * 870dd88793SEduardo Valentin thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) 880dd88793SEduardo Valentin { 890dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 90*8c7aa184SStefan Mavrodiev char type[THERMAL_NAME_LENGTH]; 910dd88793SEduardo Valentin 920dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 93*8c7aa184SStefan Mavrodiev list_for_each_entry(hwmon, &thermal_hwmon_list, node) { 94*8c7aa184SStefan Mavrodiev strcpy(type, tz->type); 95*8c7aa184SStefan Mavrodiev strreplace(type, '-', '_'); 96*8c7aa184SStefan Mavrodiev if (!strcmp(hwmon->type, type)) { 970dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 980dd88793SEduardo Valentin return hwmon; 990dd88793SEduardo Valentin } 100*8c7aa184SStefan Mavrodiev } 1010dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1020dd88793SEduardo Valentin 1030dd88793SEduardo Valentin return NULL; 1040dd88793SEduardo Valentin } 1050dd88793SEduardo Valentin 1060dd88793SEduardo Valentin /* Find the temperature input matching a given thermal zone */ 1070dd88793SEduardo Valentin static struct thermal_hwmon_temp * 1080dd88793SEduardo Valentin thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, 1090dd88793SEduardo Valentin const struct thermal_zone_device *tz) 1100dd88793SEduardo Valentin { 1110dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 1120dd88793SEduardo Valentin 1130dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 1140dd88793SEduardo Valentin list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) 1150dd88793SEduardo Valentin if (temp->tz == tz) { 1160dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1170dd88793SEduardo Valentin return temp; 1180dd88793SEduardo Valentin } 1190dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1200dd88793SEduardo Valentin 1210dd88793SEduardo Valentin return NULL; 1220dd88793SEduardo Valentin } 1230dd88793SEduardo Valentin 124e8db5d67SAaron Lu static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) 125e8db5d67SAaron Lu { 12617e8351aSSascha Hauer int temp; 127e8db5d67SAaron Lu return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp); 128e8db5d67SAaron Lu } 129e8db5d67SAaron Lu 1300dd88793SEduardo Valentin int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) 1310dd88793SEduardo Valentin { 1320dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 1330dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 1340dd88793SEduardo Valentin int new_hwmon_device = 1; 1350dd88793SEduardo Valentin int result; 1360dd88793SEduardo Valentin 1370dd88793SEduardo Valentin hwmon = thermal_hwmon_lookup_by_type(tz); 1380dd88793SEduardo Valentin if (hwmon) { 1390dd88793SEduardo Valentin new_hwmon_device = 0; 1400dd88793SEduardo Valentin goto register_sys_interface; 1410dd88793SEduardo Valentin } 1420dd88793SEduardo Valentin 1430dd88793SEduardo Valentin hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL); 1440dd88793SEduardo Valentin if (!hwmon) 1450dd88793SEduardo Valentin return -ENOMEM; 1460dd88793SEduardo Valentin 1470dd88793SEduardo Valentin INIT_LIST_HEAD(&hwmon->tz_list); 1480dd88793SEduardo Valentin strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); 149409ef0baSMarc Zyngier strreplace(hwmon->type, '-', '_'); 150f6b6b52eSMarc Zyngier hwmon->device = hwmon_device_register_with_info(&tz->device, hwmon->type, 151e782bc16SFabio Estevam hwmon, NULL, NULL); 1520dd88793SEduardo Valentin if (IS_ERR(hwmon->device)) { 1530dd88793SEduardo Valentin result = PTR_ERR(hwmon->device); 1540dd88793SEduardo Valentin goto free_mem; 1550dd88793SEduardo Valentin } 1560dd88793SEduardo Valentin 1570dd88793SEduardo Valentin register_sys_interface: 1580dd88793SEduardo Valentin temp = kzalloc(sizeof(*temp), GFP_KERNEL); 1590dd88793SEduardo Valentin if (!temp) { 1600dd88793SEduardo Valentin result = -ENOMEM; 1610dd88793SEduardo Valentin goto unregister_name; 1620dd88793SEduardo Valentin } 1630dd88793SEduardo Valentin 1640dd88793SEduardo Valentin temp->tz = tz; 1650dd88793SEduardo Valentin hwmon->count++; 1660dd88793SEduardo Valentin 1670dd88793SEduardo Valentin snprintf(temp->temp_input.name, sizeof(temp->temp_input.name), 1680dd88793SEduardo Valentin "temp%d_input", hwmon->count); 1690dd88793SEduardo Valentin temp->temp_input.attr.attr.name = temp->temp_input.name; 1700dd88793SEduardo Valentin temp->temp_input.attr.attr.mode = 0444; 1710dd88793SEduardo Valentin temp->temp_input.attr.show = temp_input_show; 1720dd88793SEduardo Valentin sysfs_attr_init(&temp->temp_input.attr.attr); 1730dd88793SEduardo Valentin result = device_create_file(hwmon->device, &temp->temp_input.attr); 1740dd88793SEduardo Valentin if (result) 1750dd88793SEduardo Valentin goto free_temp_mem; 1760dd88793SEduardo Valentin 177e8db5d67SAaron Lu if (thermal_zone_crit_temp_valid(tz)) { 1780dd88793SEduardo Valentin snprintf(temp->temp_crit.name, 1790dd88793SEduardo Valentin sizeof(temp->temp_crit.name), 1800dd88793SEduardo Valentin "temp%d_crit", hwmon->count); 1810dd88793SEduardo Valentin temp->temp_crit.attr.attr.name = temp->temp_crit.name; 1820dd88793SEduardo Valentin temp->temp_crit.attr.attr.mode = 0444; 1830dd88793SEduardo Valentin temp->temp_crit.attr.show = temp_crit_show; 1840dd88793SEduardo Valentin sysfs_attr_init(&temp->temp_crit.attr.attr); 1850dd88793SEduardo Valentin result = device_create_file(hwmon->device, 1860dd88793SEduardo Valentin &temp->temp_crit.attr); 1870dd88793SEduardo Valentin if (result) 1880dd88793SEduardo Valentin goto unregister_input; 1890dd88793SEduardo Valentin } 1900dd88793SEduardo Valentin 1910dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 1920dd88793SEduardo Valentin if (new_hwmon_device) 1930dd88793SEduardo Valentin list_add_tail(&hwmon->node, &thermal_hwmon_list); 1940dd88793SEduardo Valentin list_add_tail(&temp->hwmon_node, &hwmon->tz_list); 1950dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1960dd88793SEduardo Valentin 1970dd88793SEduardo Valentin return 0; 1980dd88793SEduardo Valentin 1990dd88793SEduardo Valentin unregister_input: 2000dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_input.attr); 2010dd88793SEduardo Valentin free_temp_mem: 2020dd88793SEduardo Valentin kfree(temp); 2030dd88793SEduardo Valentin unregister_name: 204e782bc16SFabio Estevam if (new_hwmon_device) 2050dd88793SEduardo Valentin hwmon_device_unregister(hwmon->device); 2060dd88793SEduardo Valentin free_mem: 2070dd88793SEduardo Valentin if (new_hwmon_device) 2080dd88793SEduardo Valentin kfree(hwmon); 2090dd88793SEduardo Valentin 2100dd88793SEduardo Valentin return result; 2110dd88793SEduardo Valentin } 212f4c59243SKuninori Morimoto EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); 2130dd88793SEduardo Valentin 2140dd88793SEduardo Valentin void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) 2150dd88793SEduardo Valentin { 2160dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 2170dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 2180dd88793SEduardo Valentin 2190dd88793SEduardo Valentin hwmon = thermal_hwmon_lookup_by_type(tz); 2200dd88793SEduardo Valentin if (unlikely(!hwmon)) { 2210dd88793SEduardo Valentin /* Should never happen... */ 2220dd88793SEduardo Valentin dev_dbg(&tz->device, "hwmon device lookup failed!\n"); 2230dd88793SEduardo Valentin return; 2240dd88793SEduardo Valentin } 2250dd88793SEduardo Valentin 2260dd88793SEduardo Valentin temp = thermal_hwmon_lookup_temp(hwmon, tz); 2270dd88793SEduardo Valentin if (unlikely(!temp)) { 2280dd88793SEduardo Valentin /* Should never happen... */ 2290dd88793SEduardo Valentin dev_dbg(&tz->device, "temperature input lookup failed!\n"); 2300dd88793SEduardo Valentin return; 2310dd88793SEduardo Valentin } 2320dd88793SEduardo Valentin 2330dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_input.attr); 234e8db5d67SAaron Lu if (thermal_zone_crit_temp_valid(tz)) 2350dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_crit.attr); 2360dd88793SEduardo Valentin 2370dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 2380dd88793SEduardo Valentin list_del(&temp->hwmon_node); 2390dd88793SEduardo Valentin kfree(temp); 2400dd88793SEduardo Valentin if (!list_empty(&hwmon->tz_list)) { 2410dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 2420dd88793SEduardo Valentin return; 2430dd88793SEduardo Valentin } 2440dd88793SEduardo Valentin list_del(&hwmon->node); 2450dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 2460dd88793SEduardo Valentin 2470dd88793SEduardo Valentin hwmon_device_unregister(hwmon->device); 2480dd88793SEduardo Valentin kfree(hwmon); 2490dd88793SEduardo Valentin } 250f4c59243SKuninori Morimoto EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs); 251