1*0dd88793SEduardo Valentin /* 2*0dd88793SEduardo Valentin * thermal_hwmon.c - Generic Thermal Management hwmon support. 3*0dd88793SEduardo Valentin * 4*0dd88793SEduardo Valentin * Code based on Intel thermal_core.c. Copyrights of the original code: 5*0dd88793SEduardo Valentin * Copyright (C) 2008 Intel Corp 6*0dd88793SEduardo Valentin * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> 7*0dd88793SEduardo Valentin * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> 8*0dd88793SEduardo Valentin * 9*0dd88793SEduardo Valentin * Copyright (C) 2013 Texas Instruments 10*0dd88793SEduardo Valentin * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com> 11*0dd88793SEduardo Valentin * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 12*0dd88793SEduardo Valentin * 13*0dd88793SEduardo Valentin * This program is free software; you can redistribute it and/or modify 14*0dd88793SEduardo Valentin * it under the terms of the GNU General Public License as published by 15*0dd88793SEduardo Valentin * the Free Software Foundation; version 2 of the License. 16*0dd88793SEduardo Valentin * 17*0dd88793SEduardo Valentin * This program is distributed in the hope that it will be useful, but 18*0dd88793SEduardo Valentin * WITHOUT ANY WARRANTY; without even the implied warranty of 19*0dd88793SEduardo Valentin * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20*0dd88793SEduardo Valentin * General Public License for more details. 21*0dd88793SEduardo Valentin * 22*0dd88793SEduardo Valentin * You should have received a copy of the GNU General Public License along 23*0dd88793SEduardo Valentin * with this program; if not, write to the Free Software Foundation, Inc., 24*0dd88793SEduardo Valentin * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 25*0dd88793SEduardo Valentin * 26*0dd88793SEduardo Valentin * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 27*0dd88793SEduardo Valentin */ 28*0dd88793SEduardo Valentin #include <linux/hwmon.h> 29*0dd88793SEduardo Valentin #include <linux/thermal.h> 30*0dd88793SEduardo Valentin #include <linux/slab.h> 31*0dd88793SEduardo Valentin #include <linux/err.h> 32*0dd88793SEduardo Valentin #include "thermal_hwmon.h" 33*0dd88793SEduardo Valentin 34*0dd88793SEduardo Valentin /* hwmon sys I/F */ 35*0dd88793SEduardo Valentin /* thermal zone devices with the same type share one hwmon device */ 36*0dd88793SEduardo Valentin struct thermal_hwmon_device { 37*0dd88793SEduardo Valentin char type[THERMAL_NAME_LENGTH]; 38*0dd88793SEduardo Valentin struct device *device; 39*0dd88793SEduardo Valentin int count; 40*0dd88793SEduardo Valentin struct list_head tz_list; 41*0dd88793SEduardo Valentin struct list_head node; 42*0dd88793SEduardo Valentin }; 43*0dd88793SEduardo Valentin 44*0dd88793SEduardo Valentin struct thermal_hwmon_attr { 45*0dd88793SEduardo Valentin struct device_attribute attr; 46*0dd88793SEduardo Valentin char name[16]; 47*0dd88793SEduardo Valentin }; 48*0dd88793SEduardo Valentin 49*0dd88793SEduardo Valentin /* one temperature input for each thermal zone */ 50*0dd88793SEduardo Valentin struct thermal_hwmon_temp { 51*0dd88793SEduardo Valentin struct list_head hwmon_node; 52*0dd88793SEduardo Valentin struct thermal_zone_device *tz; 53*0dd88793SEduardo Valentin struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ 54*0dd88793SEduardo Valentin struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ 55*0dd88793SEduardo Valentin }; 56*0dd88793SEduardo Valentin 57*0dd88793SEduardo Valentin static LIST_HEAD(thermal_hwmon_list); 58*0dd88793SEduardo Valentin 59*0dd88793SEduardo Valentin static DEFINE_MUTEX(thermal_hwmon_list_lock); 60*0dd88793SEduardo Valentin 61*0dd88793SEduardo Valentin static ssize_t 62*0dd88793SEduardo Valentin name_show(struct device *dev, struct device_attribute *attr, char *buf) 63*0dd88793SEduardo Valentin { 64*0dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev); 65*0dd88793SEduardo Valentin return sprintf(buf, "%s\n", hwmon->type); 66*0dd88793SEduardo Valentin } 67*0dd88793SEduardo Valentin static DEVICE_ATTR(name, 0444, name_show, NULL); 68*0dd88793SEduardo Valentin 69*0dd88793SEduardo Valentin static ssize_t 70*0dd88793SEduardo Valentin temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) 71*0dd88793SEduardo Valentin { 72*0dd88793SEduardo Valentin long temperature; 73*0dd88793SEduardo Valentin int ret; 74*0dd88793SEduardo Valentin struct thermal_hwmon_attr *hwmon_attr 75*0dd88793SEduardo Valentin = container_of(attr, struct thermal_hwmon_attr, attr); 76*0dd88793SEduardo Valentin struct thermal_hwmon_temp *temp 77*0dd88793SEduardo Valentin = container_of(hwmon_attr, struct thermal_hwmon_temp, 78*0dd88793SEduardo Valentin temp_input); 79*0dd88793SEduardo Valentin struct thermal_zone_device *tz = temp->tz; 80*0dd88793SEduardo Valentin 81*0dd88793SEduardo Valentin ret = thermal_zone_get_temp(tz, &temperature); 82*0dd88793SEduardo Valentin 83*0dd88793SEduardo Valentin if (ret) 84*0dd88793SEduardo Valentin return ret; 85*0dd88793SEduardo Valentin 86*0dd88793SEduardo Valentin return sprintf(buf, "%ld\n", temperature); 87*0dd88793SEduardo Valentin } 88*0dd88793SEduardo Valentin 89*0dd88793SEduardo Valentin static ssize_t 90*0dd88793SEduardo Valentin temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) 91*0dd88793SEduardo Valentin { 92*0dd88793SEduardo Valentin struct thermal_hwmon_attr *hwmon_attr 93*0dd88793SEduardo Valentin = container_of(attr, struct thermal_hwmon_attr, attr); 94*0dd88793SEduardo Valentin struct thermal_hwmon_temp *temp 95*0dd88793SEduardo Valentin = container_of(hwmon_attr, struct thermal_hwmon_temp, 96*0dd88793SEduardo Valentin temp_crit); 97*0dd88793SEduardo Valentin struct thermal_zone_device *tz = temp->tz; 98*0dd88793SEduardo Valentin long temperature; 99*0dd88793SEduardo Valentin int ret; 100*0dd88793SEduardo Valentin 101*0dd88793SEduardo Valentin ret = tz->ops->get_trip_temp(tz, 0, &temperature); 102*0dd88793SEduardo Valentin if (ret) 103*0dd88793SEduardo Valentin return ret; 104*0dd88793SEduardo Valentin 105*0dd88793SEduardo Valentin return sprintf(buf, "%ld\n", temperature); 106*0dd88793SEduardo Valentin } 107*0dd88793SEduardo Valentin 108*0dd88793SEduardo Valentin 109*0dd88793SEduardo Valentin static struct thermal_hwmon_device * 110*0dd88793SEduardo Valentin thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) 111*0dd88793SEduardo Valentin { 112*0dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 113*0dd88793SEduardo Valentin 114*0dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 115*0dd88793SEduardo Valentin list_for_each_entry(hwmon, &thermal_hwmon_list, node) 116*0dd88793SEduardo Valentin if (!strcmp(hwmon->type, tz->type)) { 117*0dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 118*0dd88793SEduardo Valentin return hwmon; 119*0dd88793SEduardo Valentin } 120*0dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 121*0dd88793SEduardo Valentin 122*0dd88793SEduardo Valentin return NULL; 123*0dd88793SEduardo Valentin } 124*0dd88793SEduardo Valentin 125*0dd88793SEduardo Valentin /* Find the temperature input matching a given thermal zone */ 126*0dd88793SEduardo Valentin static struct thermal_hwmon_temp * 127*0dd88793SEduardo Valentin thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, 128*0dd88793SEduardo Valentin const struct thermal_zone_device *tz) 129*0dd88793SEduardo Valentin { 130*0dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 131*0dd88793SEduardo Valentin 132*0dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 133*0dd88793SEduardo Valentin list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) 134*0dd88793SEduardo Valentin if (temp->tz == tz) { 135*0dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 136*0dd88793SEduardo Valentin return temp; 137*0dd88793SEduardo Valentin } 138*0dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 139*0dd88793SEduardo Valentin 140*0dd88793SEduardo Valentin return NULL; 141*0dd88793SEduardo Valentin } 142*0dd88793SEduardo Valentin 143*0dd88793SEduardo Valentin int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) 144*0dd88793SEduardo Valentin { 145*0dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 146*0dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 147*0dd88793SEduardo Valentin int new_hwmon_device = 1; 148*0dd88793SEduardo Valentin int result; 149*0dd88793SEduardo Valentin 150*0dd88793SEduardo Valentin hwmon = thermal_hwmon_lookup_by_type(tz); 151*0dd88793SEduardo Valentin if (hwmon) { 152*0dd88793SEduardo Valentin new_hwmon_device = 0; 153*0dd88793SEduardo Valentin goto register_sys_interface; 154*0dd88793SEduardo Valentin } 155*0dd88793SEduardo Valentin 156*0dd88793SEduardo Valentin hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL); 157*0dd88793SEduardo Valentin if (!hwmon) 158*0dd88793SEduardo Valentin return -ENOMEM; 159*0dd88793SEduardo Valentin 160*0dd88793SEduardo Valentin INIT_LIST_HEAD(&hwmon->tz_list); 161*0dd88793SEduardo Valentin strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); 162*0dd88793SEduardo Valentin hwmon->device = hwmon_device_register(NULL); 163*0dd88793SEduardo Valentin if (IS_ERR(hwmon->device)) { 164*0dd88793SEduardo Valentin result = PTR_ERR(hwmon->device); 165*0dd88793SEduardo Valentin goto free_mem; 166*0dd88793SEduardo Valentin } 167*0dd88793SEduardo Valentin dev_set_drvdata(hwmon->device, hwmon); 168*0dd88793SEduardo Valentin result = device_create_file(hwmon->device, &dev_attr_name); 169*0dd88793SEduardo Valentin if (result) 170*0dd88793SEduardo Valentin goto free_mem; 171*0dd88793SEduardo Valentin 172*0dd88793SEduardo Valentin register_sys_interface: 173*0dd88793SEduardo Valentin temp = kzalloc(sizeof(*temp), GFP_KERNEL); 174*0dd88793SEduardo Valentin if (!temp) { 175*0dd88793SEduardo Valentin result = -ENOMEM; 176*0dd88793SEduardo Valentin goto unregister_name; 177*0dd88793SEduardo Valentin } 178*0dd88793SEduardo Valentin 179*0dd88793SEduardo Valentin temp->tz = tz; 180*0dd88793SEduardo Valentin hwmon->count++; 181*0dd88793SEduardo Valentin 182*0dd88793SEduardo Valentin snprintf(temp->temp_input.name, sizeof(temp->temp_input.name), 183*0dd88793SEduardo Valentin "temp%d_input", hwmon->count); 184*0dd88793SEduardo Valentin temp->temp_input.attr.attr.name = temp->temp_input.name; 185*0dd88793SEduardo Valentin temp->temp_input.attr.attr.mode = 0444; 186*0dd88793SEduardo Valentin temp->temp_input.attr.show = temp_input_show; 187*0dd88793SEduardo Valentin sysfs_attr_init(&temp->temp_input.attr.attr); 188*0dd88793SEduardo Valentin result = device_create_file(hwmon->device, &temp->temp_input.attr); 189*0dd88793SEduardo Valentin if (result) 190*0dd88793SEduardo Valentin goto free_temp_mem; 191*0dd88793SEduardo Valentin 192*0dd88793SEduardo Valentin if (tz->ops->get_crit_temp) { 193*0dd88793SEduardo Valentin unsigned long temperature; 194*0dd88793SEduardo Valentin if (!tz->ops->get_crit_temp(tz, &temperature)) { 195*0dd88793SEduardo Valentin snprintf(temp->temp_crit.name, 196*0dd88793SEduardo Valentin sizeof(temp->temp_crit.name), 197*0dd88793SEduardo Valentin "temp%d_crit", hwmon->count); 198*0dd88793SEduardo Valentin temp->temp_crit.attr.attr.name = temp->temp_crit.name; 199*0dd88793SEduardo Valentin temp->temp_crit.attr.attr.mode = 0444; 200*0dd88793SEduardo Valentin temp->temp_crit.attr.show = temp_crit_show; 201*0dd88793SEduardo Valentin sysfs_attr_init(&temp->temp_crit.attr.attr); 202*0dd88793SEduardo Valentin result = device_create_file(hwmon->device, 203*0dd88793SEduardo Valentin &temp->temp_crit.attr); 204*0dd88793SEduardo Valentin if (result) 205*0dd88793SEduardo Valentin goto unregister_input; 206*0dd88793SEduardo Valentin } 207*0dd88793SEduardo Valentin } 208*0dd88793SEduardo Valentin 209*0dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 210*0dd88793SEduardo Valentin if (new_hwmon_device) 211*0dd88793SEduardo Valentin list_add_tail(&hwmon->node, &thermal_hwmon_list); 212*0dd88793SEduardo Valentin list_add_tail(&temp->hwmon_node, &hwmon->tz_list); 213*0dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 214*0dd88793SEduardo Valentin 215*0dd88793SEduardo Valentin return 0; 216*0dd88793SEduardo Valentin 217*0dd88793SEduardo Valentin unregister_input: 218*0dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_input.attr); 219*0dd88793SEduardo Valentin free_temp_mem: 220*0dd88793SEduardo Valentin kfree(temp); 221*0dd88793SEduardo Valentin unregister_name: 222*0dd88793SEduardo Valentin if (new_hwmon_device) { 223*0dd88793SEduardo Valentin device_remove_file(hwmon->device, &dev_attr_name); 224*0dd88793SEduardo Valentin hwmon_device_unregister(hwmon->device); 225*0dd88793SEduardo Valentin } 226*0dd88793SEduardo Valentin free_mem: 227*0dd88793SEduardo Valentin if (new_hwmon_device) 228*0dd88793SEduardo Valentin kfree(hwmon); 229*0dd88793SEduardo Valentin 230*0dd88793SEduardo Valentin return result; 231*0dd88793SEduardo Valentin } 232*0dd88793SEduardo Valentin 233*0dd88793SEduardo Valentin void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) 234*0dd88793SEduardo Valentin { 235*0dd88793SEduardo Valentin struct thermal_hwmon_device *hwmon; 236*0dd88793SEduardo Valentin struct thermal_hwmon_temp *temp; 237*0dd88793SEduardo Valentin 238*0dd88793SEduardo Valentin hwmon = thermal_hwmon_lookup_by_type(tz); 239*0dd88793SEduardo Valentin if (unlikely(!hwmon)) { 240*0dd88793SEduardo Valentin /* Should never happen... */ 241*0dd88793SEduardo Valentin dev_dbg(&tz->device, "hwmon device lookup failed!\n"); 242*0dd88793SEduardo Valentin return; 243*0dd88793SEduardo Valentin } 244*0dd88793SEduardo Valentin 245*0dd88793SEduardo Valentin temp = thermal_hwmon_lookup_temp(hwmon, tz); 246*0dd88793SEduardo Valentin if (unlikely(!temp)) { 247*0dd88793SEduardo Valentin /* Should never happen... */ 248*0dd88793SEduardo Valentin dev_dbg(&tz->device, "temperature input lookup failed!\n"); 249*0dd88793SEduardo Valentin return; 250*0dd88793SEduardo Valentin } 251*0dd88793SEduardo Valentin 252*0dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_input.attr); 253*0dd88793SEduardo Valentin if (tz->ops->get_crit_temp) 254*0dd88793SEduardo Valentin device_remove_file(hwmon->device, &temp->temp_crit.attr); 255*0dd88793SEduardo Valentin 256*0dd88793SEduardo Valentin mutex_lock(&thermal_hwmon_list_lock); 257*0dd88793SEduardo Valentin list_del(&temp->hwmon_node); 258*0dd88793SEduardo Valentin kfree(temp); 259*0dd88793SEduardo Valentin if (!list_empty(&hwmon->tz_list)) { 260*0dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 261*0dd88793SEduardo Valentin return; 262*0dd88793SEduardo Valentin } 263*0dd88793SEduardo Valentin list_del(&hwmon->node); 264*0dd88793SEduardo Valentin mutex_unlock(&thermal_hwmon_list_lock); 265*0dd88793SEduardo Valentin 266*0dd88793SEduardo Valentin device_remove_file(hwmon->device, &dev_attr_name); 267*0dd88793SEduardo Valentin hwmon_device_unregister(hwmon->device); 268*0dd88793SEduardo Valentin kfree(hwmon); 269*0dd88793SEduardo Valentin } 270