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