10dd88793SEduardo Valentin /* 20dd88793SEduardo Valentin * thermal_hwmon.c - Generic Thermal Management hwmon support. 30dd88793SEduardo Valentin * 40dd88793SEduardo Valentin * Code based on Intel thermal_core.c. Copyrights of the original code: 50dd88793SEduardo Valentin * Copyright (C) 2008 Intel Corp 60dd88793SEduardo Valentin * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> 70dd88793SEduardo Valentin * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> 80dd88793SEduardo Valentin * 90dd88793SEduardo Valentin * Copyright (C) 2013 Texas Instruments 100dd88793SEduardo Valentin * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com> 110dd88793SEduardo Valentin * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 120dd88793SEduardo Valentin * 130dd88793SEduardo Valentin * This program is free software; you can redistribute it and/or modify 140dd88793SEduardo Valentin * it under the terms of the GNU General Public License as published by 150dd88793SEduardo Valentin * the Free Software Foundation; version 2 of the License. 160dd88793SEduardo Valentin * 170dd88793SEduardo Valentin * This program is distributed in the hope that it will be useful, but 180dd88793SEduardo Valentin * WITHOUT ANY WARRANTY; without even the implied warranty of 190dd88793SEduardo Valentin * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 200dd88793SEduardo Valentin * General Public License for more details. 210dd88793SEduardo Valentin * 220dd88793SEduardo Valentin * You should have received a copy of the GNU General Public License along 230dd88793SEduardo Valentin * with this program; if not, write to the Free Software Foundation, Inc., 240dd88793SEduardo Valentin * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 250dd88793SEduardo Valentin * 260dd88793SEduardo Valentin * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 270dd88793SEduardo Valentin */ 280dd88793SEduardo Valentin #include <linux/hwmon.h> 290dd88793SEduardo Valentin #include <linux/thermal.h> 300dd88793SEduardo Valentin #include <linux/slab.h> 310dd88793SEduardo Valentin #include <linux/err.h> 320dd88793SEduardo Valentin #include "thermal_hwmon.h" 330dd88793SEduardo Valentin 340dd88793SEduardo Valentin /* hwmon sys I/F */ 350dd88793SEduardo Valentin /* thermal zone devices with the same type share one hwmon device */ 360dd88793SEduardo Valentin struct thermal_hwmon_device { 370dd88793SEduardo Valentin char type[THERMAL_NAME_LENGTH]; 380dd88793SEduardo Valentin struct device *device; 390dd88793SEduardo Valentin int count; 400dd88793SEduardo Valentin struct list_head tz_list; 410dd88793SEduardo Valentin struct list_head node; 420dd88793SEduardo Valentin }; 430dd88793SEduardo Valentin 440dd88793SEduardo Valentin struct thermal_hwmon_attr { 450dd88793SEduardo Valentin struct device_attribute attr; 460dd88793SEduardo Valentin char name[16]; 470dd88793SEduardo Valentin }; 480dd88793SEduardo Valentin 490dd88793SEduardo Valentin /* one temperature input for each thermal zone */ 500dd88793SEduardo Valentin struct thermal_hwmon_temp { 510dd88793SEduardo Valentin struct list_head hwmon_node; 520dd88793SEduardo Valentin struct thermal_zone_device *tz; 530dd88793SEduardo Valentin struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ 540dd88793SEduardo Valentin struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ 550dd88793SEduardo Valentin }; 560dd88793SEduardo Valentin 570dd88793SEduardo Valentin static LIST_HEAD(thermal_hwmon_list); 580dd88793SEduardo Valentin 590dd88793SEduardo Valentin static DEFINE_MUTEX(thermal_hwmon_list_lock); 600dd88793SEduardo Valentin 610dd88793SEduardo Valentin static ssize_t 620dd88793SEduardo Valentin name_show(struct device *dev, struct device_attribute *attr, char *buf) 630dd88793SEduardo Valentin { 640dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev); 650dd88793SEduardo Valentin return sprintf(buf, "%s\n", hwmon->type); 660dd88793SEduardo Valentin } 670dd88793SEduardo Valentin static DEVICE_ATTR(name, 0444, name_show, NULL); 680dd88793SEduardo Valentin 690dd88793SEduardo Valentin static ssize_t 700dd88793SEduardo Valentin temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) 710dd88793SEduardo Valentin { 7217e8351aSSascha Hauer int temperature; 730dd88793SEduardo Valentin int ret; 740dd88793SEduardo Valentin struct thermal_hwmon_attr *hwmon_attr 750dd88793SEduardo Valentin = container_of(attr, struct thermal_hwmon_attr, attr); 760dd88793SEduardo Valentin struct thermal_hwmon_temp *temp 770dd88793SEduardo Valentin = container_of(hwmon_attr, struct thermal_hwmon_temp, 780dd88793SEduardo Valentin temp_input); 790dd88793SEduardo Valentin struct thermal_zone_device *tz = temp->tz; 800dd88793SEduardo Valentin 810dd88793SEduardo Valentin ret = thermal_zone_get_temp(tz, &temperature); 820dd88793SEduardo Valentin 830dd88793SEduardo Valentin if (ret) 840dd88793SEduardo Valentin return ret; 850dd88793SEduardo Valentin 8617e8351aSSascha Hauer return sprintf(buf, "%d\n", temperature); 870dd88793SEduardo Valentin } 880dd88793SEduardo Valentin 890dd88793SEduardo Valentin static ssize_t 900dd88793SEduardo Valentin temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) 910dd88793SEduardo Valentin { 920dd88793SEduardo Valentin struct thermal_hwmon_attr *hwmon_attr 930dd88793SEduardo Valentin = container_of(attr, struct thermal_hwmon_attr, attr); 940dd88793SEduardo Valentin struct thermal_hwmon_temp *temp 950dd88793SEduardo Valentin = container_of(hwmon_attr, struct thermal_hwmon_temp, 960dd88793SEduardo Valentin temp_crit); 970dd88793SEduardo Valentin struct thermal_zone_device *tz = temp->tz; 9817e8351aSSascha Hauer int temperature; 990dd88793SEduardo Valentin int ret; 1000dd88793SEduardo Valentin 1010dd88793SEduardo Valentin ret = tz->ops->get_trip_temp(tz, 0, &temperature); 1020dd88793SEduardo Valentin if (ret) 1030dd88793SEduardo Valentin return ret; 1040dd88793SEduardo Valentin 10517e8351aSSascha Hauer return sprintf(buf, "%d\n", temperature); 1060dd88793SEduardo Valentin } 1070dd88793SEduardo Valentin 1080dd88793SEduardo Valentin 1090dd88793SEduardo Valentin static struct thermal_hwmon_device * 1100dd88793SEduardo Valentin thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) 1110dd88793SEduardo Valentin { 1120dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 1130dd88793SEduardo Valentin 1140dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 1150dd88793SEduardo Valentin list_for_each_entry(hwmon, &thermal_hwmon_list, node) 1160dd88793SEduardo Valentin if (!strcmp(hwmon->type, tz->type)) { 1170dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1180dd88793SEduardo Valentin return hwmon; 1190dd88793SEduardo Valentin } 1200dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1210dd88793SEduardo Valentin 1220dd88793SEduardo Valentin return NULL; 1230dd88793SEduardo Valentin } 1240dd88793SEduardo Valentin 1250dd88793SEduardo Valentin /* Find the temperature input matching a given thermal zone */ 1260dd88793SEduardo Valentin static struct thermal_hwmon_temp * 1270dd88793SEduardo Valentin thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, 1280dd88793SEduardo Valentin const struct thermal_zone_device *tz) 1290dd88793SEduardo Valentin { 1300dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 1310dd88793SEduardo Valentin 1320dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 1330dd88793SEduardo Valentin list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) 1340dd88793SEduardo Valentin if (temp->tz == tz) { 1350dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1360dd88793SEduardo Valentin return temp; 1370dd88793SEduardo Valentin } 1380dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 1390dd88793SEduardo Valentin 1400dd88793SEduardo Valentin return NULL; 1410dd88793SEduardo Valentin } 1420dd88793SEduardo Valentin 143e8db5d67SAaron Lu static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) 144e8db5d67SAaron Lu { 14517e8351aSSascha Hauer int temp; 146e8db5d67SAaron Lu return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp); 147e8db5d67SAaron Lu } 148e8db5d67SAaron Lu 1490dd88793SEduardo Valentin int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) 1500dd88793SEduardo Valentin { 1510dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 1520dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 1530dd88793SEduardo Valentin int new_hwmon_device = 1; 1540dd88793SEduardo Valentin int result; 1550dd88793SEduardo Valentin 1560dd88793SEduardo Valentin hwmon = thermal_hwmon_lookup_by_type(tz); 1570dd88793SEduardo Valentin if (hwmon) { 1580dd88793SEduardo Valentin new_hwmon_device = 0; 1590dd88793SEduardo Valentin goto register_sys_interface; 1600dd88793SEduardo Valentin } 1610dd88793SEduardo Valentin 1620dd88793SEduardo Valentin hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL); 1630dd88793SEduardo Valentin if (!hwmon) 1640dd88793SEduardo Valentin return -ENOMEM; 1650dd88793SEduardo Valentin 1660dd88793SEduardo Valentin INIT_LIST_HEAD(&hwmon->tz_list); 1670dd88793SEduardo Valentin strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); 1686ddcb7e6SZhang Rui hwmon->device = hwmon_device_register(NULL); 1690dd88793SEduardo Valentin if (IS_ERR(hwmon->device)) { 1700dd88793SEduardo Valentin result = PTR_ERR(hwmon->device); 1710dd88793SEduardo Valentin goto free_mem; 1720dd88793SEduardo Valentin } 1730dd88793SEduardo Valentin dev_set_drvdata(hwmon->device, hwmon); 1740dd88793SEduardo Valentin result = device_create_file(hwmon->device, &dev_attr_name); 1750dd88793SEduardo Valentin if (result) 1760dd88793SEduardo Valentin goto free_mem; 1770dd88793SEduardo Valentin 1780dd88793SEduardo Valentin register_sys_interface: 1790dd88793SEduardo Valentin temp = kzalloc(sizeof(*temp), GFP_KERNEL); 1800dd88793SEduardo Valentin if (!temp) { 1810dd88793SEduardo Valentin result = -ENOMEM; 1820dd88793SEduardo Valentin goto unregister_name; 1830dd88793SEduardo Valentin } 1840dd88793SEduardo Valentin 1850dd88793SEduardo Valentin temp->tz = tz; 1860dd88793SEduardo Valentin hwmon->count++; 1870dd88793SEduardo Valentin 1880dd88793SEduardo Valentin snprintf(temp->temp_input.name, sizeof(temp->temp_input.name), 1890dd88793SEduardo Valentin "temp%d_input", hwmon->count); 1900dd88793SEduardo Valentin temp->temp_input.attr.attr.name = temp->temp_input.name; 1910dd88793SEduardo Valentin temp->temp_input.attr.attr.mode = 0444; 1920dd88793SEduardo Valentin temp->temp_input.attr.show = temp_input_show; 1930dd88793SEduardo Valentin sysfs_attr_init(&temp->temp_input.attr.attr); 1940dd88793SEduardo Valentin result = device_create_file(hwmon->device, &temp->temp_input.attr); 1950dd88793SEduardo Valentin if (result) 1960dd88793SEduardo Valentin goto free_temp_mem; 1970dd88793SEduardo Valentin 198e8db5d67SAaron Lu if (thermal_zone_crit_temp_valid(tz)) { 1990dd88793SEduardo Valentin snprintf(temp->temp_crit.name, 2000dd88793SEduardo Valentin sizeof(temp->temp_crit.name), 2010dd88793SEduardo Valentin "temp%d_crit", hwmon->count); 2020dd88793SEduardo Valentin temp->temp_crit.attr.attr.name = temp->temp_crit.name; 2030dd88793SEduardo Valentin temp->temp_crit.attr.attr.mode = 0444; 2040dd88793SEduardo Valentin temp->temp_crit.attr.show = temp_crit_show; 2050dd88793SEduardo Valentin sysfs_attr_init(&temp->temp_crit.attr.attr); 2060dd88793SEduardo Valentin result = device_create_file(hwmon->device, 2070dd88793SEduardo Valentin &temp->temp_crit.attr); 2080dd88793SEduardo Valentin if (result) 2090dd88793SEduardo Valentin goto unregister_input; 2100dd88793SEduardo Valentin } 2110dd88793SEduardo Valentin 2120dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 2130dd88793SEduardo Valentin if (new_hwmon_device) 2140dd88793SEduardo Valentin list_add_tail(&hwmon->node, &thermal_hwmon_list); 2150dd88793SEduardo Valentin list_add_tail(&temp->hwmon_node, &hwmon->tz_list); 2160dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 2170dd88793SEduardo Valentin 2180dd88793SEduardo Valentin return 0; 2190dd88793SEduardo Valentin 2200dd88793SEduardo Valentin unregister_input: 2210dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_input.attr); 2220dd88793SEduardo Valentin free_temp_mem: 2230dd88793SEduardo Valentin kfree(temp); 2240dd88793SEduardo Valentin unregister_name: 2250dd88793SEduardo Valentin if (new_hwmon_device) { 2260dd88793SEduardo Valentin device_remove_file(hwmon->device, &dev_attr_name); 2270dd88793SEduardo Valentin hwmon_device_unregister(hwmon->device); 2280dd88793SEduardo Valentin } 2290dd88793SEduardo Valentin free_mem: 2300dd88793SEduardo Valentin if (new_hwmon_device) 2310dd88793SEduardo Valentin kfree(hwmon); 2320dd88793SEduardo Valentin 2330dd88793SEduardo Valentin return result; 2340dd88793SEduardo Valentin } 235*f4c59243SKuninori Morimoto EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); 2360dd88793SEduardo Valentin 2370dd88793SEduardo Valentin void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) 2380dd88793SEduardo Valentin { 2390dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 2400dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 2410dd88793SEduardo Valentin 2420dd88793SEduardo Valentin hwmon = thermal_hwmon_lookup_by_type(tz); 2430dd88793SEduardo Valentin if (unlikely(!hwmon)) { 2440dd88793SEduardo Valentin /* Should never happen... */ 2450dd88793SEduardo Valentin dev_dbg(&tz->device, "hwmon device lookup failed!\n"); 2460dd88793SEduardo Valentin return; 2470dd88793SEduardo Valentin } 2480dd88793SEduardo Valentin 2490dd88793SEduardo Valentin temp = thermal_hwmon_lookup_temp(hwmon, tz); 2500dd88793SEduardo Valentin if (unlikely(!temp)) { 2510dd88793SEduardo Valentin /* Should never happen... */ 2520dd88793SEduardo Valentin dev_dbg(&tz->device, "temperature input lookup failed!\n"); 2530dd88793SEduardo Valentin return; 2540dd88793SEduardo Valentin } 2550dd88793SEduardo Valentin 2560dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_input.attr); 257e8db5d67SAaron Lu if (thermal_zone_crit_temp_valid(tz)) 2580dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_crit.attr); 2590dd88793SEduardo Valentin 2600dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 2610dd88793SEduardo Valentin list_del(&temp->hwmon_node); 2620dd88793SEduardo Valentin kfree(temp); 2630dd88793SEduardo Valentin if (!list_empty(&hwmon->tz_list)) { 2640dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 2650dd88793SEduardo Valentin return; 2660dd88793SEduardo Valentin } 2670dd88793SEduardo Valentin list_del(&hwmon->node); 2680dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 2690dd88793SEduardo Valentin 2700dd88793SEduardo Valentin device_remove_file(hwmon->device, &dev_attr_name); 2710dd88793SEduardo Valentin hwmon_device_unregister(hwmon->device); 2720dd88793SEduardo Valentin kfree(hwmon); 2730dd88793SEduardo Valentin } 274*f4c59243SKuninori Morimoto EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs); 275